CVE-2017-20159: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in rf Keynote up to 0.x on Rails. It has been rated as problematic. Affected by this issue is some unknown functionality of the file lib/keynote/rumble.rb. The manipulation of the argument value leads to cross site scripting. The attack may be launched remotely. Upgrading to version 1.0.0 is able to address this issue. The patch is identified as 05be4356b0a6ca7de48da926a9b997beb5ffeb4a. It is recommended to upgrade the affected component. VDB-217142 is the identifier assigned to this vulnerability.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2022-12-31T11:15Z
Repository: https://github.com/rf-/keynote
Language: Ruby
Files changed: lib/keynote/rumble.rb, spec/rumble_spec.rb
Commit date: 2017-07-22 15:00:52 -0700
Commit message: Always escape quotes in attributes; support array attrs

The current behavior is to pass HTML-safe strings through unaltered, but
this can still cause an XSS vulnerability if the string has an unescaped
quote in it:

    https://github.com/rails/rails/commit/4394e9075189ee6031944491aecd64fb269cdf54

I also added support for array attributes, since I noticed Rails does
it when I was copying their escaping code.

Diff: diff --git a/lib/keynote/rumble.rb b/lib/keynote/rumble.rb
index 459e508..ca3e6ae 100644
--- a/lib/keynote/rumble.rb
+++ b/lib/keynote/rumble.rb
@@ -321,14 +321,22 @@ module Keynote
 
       def inspect; to_s.inspect end
 
       def attrs_to_s
         attributes.inject("") do |res, (name, value)|
-          if value
-            value = (value == true) ? name : Rumble.html_escape(value)
-            res << " #{name}=\"#{value}\""
-          end
+          next unless value
+
+          value =
+            if value.is_a?(Array)
+              value.map { |val| Rumble.html_escape(val) }.join(" ")
+            elsif value == true
+              name
+            else
+              Rumble.html_escape(value)
+            end
+
+          res << " #{name}=\"#{value.gsub('"'.freeze, '&quot;'.freeze)}\""
           res
         end
       end
     end
 
diff --git a/spec/rumble_spec.rb b/spec/rumble_spec.rb
index e3ef945..047e326 100644
--- a/spec/rumble_spec.rb
+++ b/spec/rumble_spec.rb
@@ -65,15 +65,27 @@ class TestRumble < klass
     end
   end
 
   def test_hash_data
     str = <<-HTML
-      <div data-modal="true" data-test="&quot;test&quot;"></div>
+      <div data-modal="true" data-safe="&quot;&quot;&quot;" data-unsafe="&quot;&amp;quot;&quot;">
+      </div>
+    HTML
+
+    assert_rumble str do
+      div data: { modal: true, safe: '"&quot;"'.html_safe, unsafe: '"&quot;"' }
+    end
+  end
+
+  def test_array_attrs
+    str = <<-HTML
+      <div class="hello &quot;uns&amp;amp;fe&quot; &quot;w&amp;rld&quot;">
+      </div>
     HTML
 
     assert_rumble str do
-      div data: { modal: true, test: '"test"' }
+      div class: ["hello", '"uns&amp;fe"', '"w&amp;rld"'.html_safe]
     end
   end
 
   def test_several
     str = <<-HTML

Vulnerable code:           if value
            value = (value == true) ? name : Rumble.html_escape(value)
            res << " #{name}=\"#{value}\""
          end
      <div data-modal="true" data-test="&quot;test&quot;"></div>
      div data: { modal: true, test: '"test"' }
Fixed code:           next unless value

          value =
            if value.is_a?(Array)
              value.map { |val| Rumble.html_escape(val) }.join(" ")
            elsif value == true
              name
            else
              Rumble.html_escape(value)
            end

          res << " #{name}=\"#{value.gsub('"'.freeze, '&quot;'.freeze)}\""
      <div data-modal="true" data-safe="&quot;&quot;&quot;" data-unsafe="&quot;&amp;quot;&quot;">
      </div>
    HTML

    assert_rumble str do
      div data: { modal: true, safe: '"&quot;"'.html_safe, unsafe: '"&quot;"' }
    end
  end

  def test_array_attrs
    str = <<-HTML
      <div class="hello &quot;uns&amp;amp;fe&quot; &quot;w&amp;rld&quot;">
      </div>
      div class: ["hello", '"uns&amp;fe"', '"w&amp;rld"'.html_safe]