VYPR
Moderate severityNVD Advisory· Published Oct 15, 2019· Updated Aug 5, 2024

CVE-2017-1002201

CVE-2017-1002201

Description

In haml versions prior to version 5.0.0.beta.2, when using user input to perform tasks on the server, characters like < > " ' must be escaped properly. In this case, the ' character was missed. An attacker can manipulate the input to introduce additional attributes, potentially executing code.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Haml prior to 5.0.0.beta.2 fails to escape apostrophes, allowing XSS via injection of attributes in rendered HTML.

The vulnerability lies in Haml's html_escape helper, which fails to escape the single quote character (') when processing user-supplied input. This omission allows an attacker to inject arbitrary HTML attributes by breaking out of an attribute context using a single quote [1][4].

To exploit this, an attacker can supply a string containing a single quote followed by additional attributes, such as event handlers. For example, if the input h'i' is rendered in an attribute value, the single quote is not escaped, potentially leading to the injection of onclick or similar attributes that execute JavaScript [2].

Successful exploitation results in cross-site scripting (XSS), enabling the attacker to execute arbitrary JavaScript in the context of the victim's browser. This can lead to session hijacking, data theft, or other malicious actions.

The issue was addressed in Haml version 5.0.0.beta.2, released on February 26, 2017. Users should upgrade to this version or later to mitigate the risk [2][4].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
hamlRubyGems
< 5.0.05.0.0

Affected products

233

Patches

1
18576ae6e9bd

Always escape `'` in Haml::Helpers.#html_escape.

https://github.com/haml/hamlTakashi KokubunFeb 8, 2017via ghsa
5 files changed · +19 19
  • lib/haml/helpers.rb+1 1 modified
    @@ -596,7 +596,7 @@ def haml_tag_if(condition, *tag)
         # Characters that need to be escaped to HTML entities from user input
         HTML_ESCAPE = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#039;' }
     
    -    HTML_ESCAPE_REGEX = /[\"><&]/
    +    HTML_ESCAPE_REGEX = /['"><&]/
     
         # Returns a copy of `text` with ampersands, angle brackets and quotes
         # escaped into HTML entities.
    
  • test/engine_test.rb+7 7 modified
    @@ -1127,8 +1127,8 @@ def test_doctypes
       def test_attr_wrapper
         assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
         assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
    -    assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
    -    assert_equal("<p escaped=\"q'uo&#x0022;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
    +    assert_equal("<p escaped=\"quo&#039;te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
    +    assert_equal("<p escaped='q&#039;uo\"te'></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
         assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"', :format => :xhtml))
       end
     
    @@ -1534,7 +1534,7 @@ def test_html5_data_attributes_without_hyphenation
           render("%div{:data => {:one_plus_one => 1+1}}",
             :hyphenate_data_attrs => false))
     
    -    assert_equal("<div data-foo='Here&#x0027;s a \"quoteful\" string.'></div>\n",
    +    assert_equal("<div data-foo='Here&#039;s a \"quoteful\" string.'></div>\n",
           render(%{%div{:data => {:foo => %{Here's a "quoteful" string.}}}},
             :hyphenate_data_attrs => false)) #'
       end
    @@ -1698,9 +1698,9 @@ def test_truthy_new_attributes
       def test_new_attribute_parsing
         assert_equal("<a a2='b2'>bar</a>\n", render("%a(a2=b2) bar", :locals => {:b2 => 'b2'}))
         assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a="#{'foo"bar'}") bar})) #'
    -    assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
    +    assert_equal(%Q{<a a='foo&#039;bar'>bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
         assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a='foo"bar') bar}))
    -    assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
    +    assert_equal(%Q{<a a='foo&#039;bar'>bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
         assert_equal("<a a:b='foo'>bar</a>\n", render("%a(a:b='foo') bar"))
         assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = 'foo' b = 'bar') bar"))
         assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = foo b = bar) bar", :locals => {:foo => 'foo', :bar => 'bar'}))
    @@ -1713,8 +1713,8 @@ def test_new_attribute_escaping
         assert_equal(%Q{<a a='foo " bar'>bar</a>\n}, render(%q{%a(a="foo \" bar") bar}))
         assert_equal(%Q{<a a='foo \\" bar'>bar</a>\n}, render(%q{%a(a="foo \\\\\" bar") bar}))
     
    -    assert_equal(%Q{<a a="foo ' bar">bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
    -    assert_equal(%Q{<a a="foo \\' bar">bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
    +    assert_equal(%Q{<a a='foo &#039; bar'>bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
    +    assert_equal(%Q{<a a='foo \\&#039; bar'>bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
     
         assert_equal(%Q{<a a='foo \\ bar'>bar</a>\n}, render(%q{%a(a="foo \\\\ bar") bar}))
         assert_equal(%Q{<a a='foo \#{1 + 1} bar'>bar</a>\n}, render(%q{%a(a="foo \#{1 + 1} bar") bar}))
    
  • test/pretty_engine_test.rb+7 7 modified
    @@ -1129,8 +1129,8 @@ def test_doctypes
       def test_attr_wrapper
         assert_equal("<p strange=*attrs*></p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
         assert_equal("<p escaped='quo\"te'></p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
    -    assert_equal("<p escaped=\"quo'te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
    -    assert_equal("<p escaped=\"q'uo&#x0022;te\"></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
    +    assert_equal("<p escaped=\"quo&#039;te\"></p>\n", render("%p{ :escaped => 'quo\\'te'}", :attr_wrapper => '"'))
    +    assert_equal("<p escaped='q&#039;uo\"te'></p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
         assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"', :format => :xhtml))
       end
     
    @@ -1527,7 +1527,7 @@ def test_html5_data_attributes_without_hyphenation
           render("%div{:data => {:one_plus_one => 1+1}}",
             :hyphenate_data_attrs => false))
     
    -    assert_equal("<div data-foo='Here&#x0027;s a \"quoteful\" string.'></div>\n",
    +    assert_equal("<div data-foo='Here&#039;s a \"quoteful\" string.'></div>\n",
           render(%{%div{:data => {:foo => %{Here's a "quoteful" string.}}}},
             :hyphenate_data_attrs => false)) #'
       end
    @@ -1691,9 +1691,9 @@ def test_truthy_new_attributes
       def test_new_attribute_parsing
         assert_equal("<a a2='b2'>bar</a>\n", render("%a(a2=b2) bar", :locals => {:b2 => 'b2'}))
         assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a="#{'foo"bar'}") bar})) #'
    -    assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
    +    assert_equal(%Q{<a a='foo&#039;bar'>bar</a>\n}, render(%q{%a(a="#{"foo'bar"}") bar})) #'
         assert_equal(%Q{<a a='foo"bar'>bar</a>\n}, render(%q{%a(a='foo"bar') bar}))
    -    assert_equal(%Q{<a a="foo'bar">bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
    +    assert_equal(%Q{<a a='foo&#039;bar'>bar</a>\n}, render(%q{%a(a="foo'bar") bar}))
         assert_equal("<a a:b='foo'>bar</a>\n", render("%a(a:b='foo') bar"))
         assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = 'foo' b = 'bar') bar"))
         assert_equal("<a a='foo' b='bar'>bar</a>\n", render("%a(a = foo b = bar) bar", :locals => {:foo => 'foo', :bar => 'bar'}))
    @@ -1706,8 +1706,8 @@ def test_new_attribute_escaping
         assert_equal(%Q{<a a='foo " bar'>bar</a>\n}, render(%q{%a(a="foo \" bar") bar}))
         assert_equal(%Q{<a a='foo \\" bar'>bar</a>\n}, render(%q{%a(a="foo \\\\\" bar") bar}))
     
    -    assert_equal(%Q{<a a="foo ' bar">bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
    -    assert_equal(%Q{<a a="foo \\' bar">bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
    +    assert_equal(%Q{<a a='foo &#039; bar'>bar</a>\n}, render(%q{%a(a='foo \' bar') bar}))
    +    assert_equal(%Q{<a a='foo \\&#039; bar'>bar</a>\n}, render(%q{%a(a='foo \\\\\' bar') bar}))
     
         assert_equal(%Q{<a a='foo \\ bar'>bar</a>\n}, render(%q{%a(a="foo \\\\ bar") bar}))
         assert_equal(%Q{<a a='foo \#{1 + 1} bar'>bar</a>\n}, render(%q{%a(a="foo \#{1 + 1} bar") bar}))
    
  • test/pretty_results/just_stuff.xhtml+2 2 modified
    @@ -6,7 +6,7 @@
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
    -<strong apos="Foo's bar!">Boo!</strong>
    +<strong apos='Foo&#039;s bar!'>Boo!</strong>
     Embedded? false!
     Embedded? true!
     Embedded? true!
    @@ -61,7 +61,7 @@ testtest
     <p class='article quux qux' id='article_1'>Blump</p>
     <p class='article' id='foo_bar_baz_article_1'>Whee</p>
     Woah inner quotes
    -<p class='dynamic_quote' dyn='3' quotes="single '"></p>
    +<p class='dynamic_quote' dyn='3' quotes='single &#039;'></p>
     <p class='dynamic_self_closing' dyn='3' />
     <body>
       hello
    
  • test/results/just_stuff.xhtml+2 2 modified
    @@ -6,7 +6,7 @@
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
    -<strong apos="Foo's bar!">Boo!</strong>
    +<strong apos='Foo&#039;s bar!'>Boo!</strong>
     Embedded? false!
     Embedded? true!
     Embedded? true!
    @@ -61,7 +61,7 @@ Nested content
     <p class='article quux qux' id='article_1'>Blump</p>
     <p class='article' id='foo_bar_baz_article_1'>Whee</p>
     Woah inner quotes
    -<p class='dynamic_quote' dyn='3' quotes="single '"></p>
    +<p class='dynamic_quote' dyn='3' quotes='single &#039;'></p>
     <p class='dynamic_self_closing' dyn='3' />
     <body>
     hello
    

Vulnerability mechanics

Root cause

"The Haml library failed to properly escape the single quote character when rendering user-provided input within HTML attributes."

Attack vector

An attacker can exploit this vulnerability by providing crafted input containing single quote characters to an application using Haml. By injecting these characters, an attacker can break out of the intended attribute context to introduce additional attributes. This manipulation can potentially lead to code execution depending on how the application processes the resulting HTML [patch_id=14865].

Affected code

The vulnerability exists in the Haml library, specifically in how it handles attribute escaping for user-provided input. The issue involves the failure to properly escape the single quote character (`'`) within attribute values. This flaw affects versions prior to 5.0.0.beta.2 [patch_id=14865].

What the fix does

The patch updates the escaping logic to include the single quote character (`'`) in the set of characters that are properly escaped when processing attributes. By ensuring that single quotes are transformed into their corresponding HTML entities, the patch prevents attackers from prematurely closing attribute values. This change effectively mitigates the risk of attribute injection [patch_id=14865].

Preconditions

  • configThe application must be using a version of Haml prior to 5.0.0.beta.2.
  • inputThe application must pass unsanitized user input into Haml templates where it is rendered as an HTML attribute.

Generated on May 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.