CVE-2022-3704: Improper Neutralization (CWE-707)
value: A vulnerability classified as problematic has been found in Ruby on Rails. This affects an unknown part of the file actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb. The manipulation leads to cross site scripting. It is possible to initiate the attack remotely. The real existence of this vulnerability is still doubted at the moment. The name of the patch is be177e4566747b73ff63fd5f529fab564e475ed4. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-212319. NOTE: Maintainer declares that there isn’t a valid attack vector. The issue was wrongly reported as a security vulnerability by a non-member of the Rails team.
CWE description: The product does not ensure or incorrectly ensures that structured messages or data are well-formed and that certain security properties are met before being read from an upstream component or sent to a downstream component.
CVSS v3 base score: 5.4
Published: 2022-10-26T20:15Z
Repository: https://github.com/rails/rails
Language: Ruby
Files changed: actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
Commit date: 2022-10-21 21:18:48 +0200
Commit message: Fix #46244 Remove innerHTML usage to avoid self-XSS

Diff: diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 2ff405df3fb..b6710262ada 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -100,13 +100,13 @@
   NodeList.prototype.forEach = Array.prototype.forEach;
 
   // Enables path search functionality
   function setupMatchPaths() {
     // Check if there are any matched results in a section
-    function checkNoMatch(section, noMatchText) {
+    function checkNoMatch(section, trElement) {
       if (section.children.length <= 1) {
-        section.innerHTML += noMatchText;
+        section.appendChild(trElement);
       }
     }
 
     // get JSON from URL and invoke callback with result
     function getJSON(url, success) {
@@ -143,25 +143,34 @@
         exactSection.innerHTML = "";
         fuzzySection.innerHTML = "";
       }
     }
 
+    function buildTr(string) {
+      var tr = document.createElement('tr');
+      var th = document.createElement('th');
+      th.setAttribute('colspan', 4);
+      tr.appendChild(th);
+      th.innerText = string;
+      return tr;
+    }
+
     // On key press perform a search for matching paths
     delayedKeyup(searchElem, function() {
       var path = sanitizePath(searchElem.value),
-          defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>',
-          defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</th></tr>',
-          noExactMatch      = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
-          noFuzzyMatch      = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
+          defaultExactMatch = buildTr('Paths Matching (' + path + '):'),
+          defaultFuzzyMatch = buildTr('Paths Containing (' + path +'):'),
+          noExactMatch      = buildTr('No Exact Matches Found'),
+          noFuzzyMatch      = buildTr('No Fuzzy Matches Found');
 
       if (!path)
         return searchElem.onblur();
 
       getJSON('/rails/info/routes?path=' + path, function(matches){
         // Clear out results section
-        exactSection.innerHTML = defaultExactMatch;
-        fuzzySection.innerHTML = defaultFuzzyMatch;
+        exactSection.replaceChildren(defaultExactMatch);
+        fuzzySection.replaceChildren(defaultFuzzyMatch);
 
         // Display exact matches and fuzzy matches
         pathElements.forEach(function(elem) {
           var elemPath = elem.getAttribute('data-route-path');
 

Vulnerable code:     function checkNoMatch(section, noMatchText) {
        section.innerHTML += noMatchText;
          defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>',
          defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</th></tr>',
          noExactMatch      = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
          noFuzzyMatch      = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
        exactSection.innerHTML = defaultExactMatch;
        fuzzySection.innerHTML = defaultFuzzyMatch;
Fixed code:     function checkNoMatch(section, trElement) {
        section.appendChild(trElement);
    function buildTr(string) {
      var tr = document.createElement('tr');
      var th = document.createElement('th');
      th.setAttribute('colspan', 4);
      tr.appendChild(th);
      th.innerText = string;
      return tr;
    }

          defaultExactMatch = buildTr('Paths Matching (' + path + '):'),
          defaultFuzzyMatch = buildTr('Paths Containing (' + path +'):'),
          noExactMatch      = buildTr('No Exact Matches Found'),
          noFuzzyMatch      = buildTr('No Fuzzy Matches Found');
        exactSection.replaceChildren(defaultExactMatch);
        fuzzySection.replaceChildren(defaultFuzzyMatch);