VYPR
High severityNVD Advisory· Published Dec 14, 2022· Updated Nov 3, 2025

Inefficient Regular Expression Complexity in Loofah

CVE-2022-23514

Description

Loofah is a general library for manipulating and transforming HTML/XML documents and fragments, built on top of Nokogiri. Loofah < 2.19.1 contains an inefficient regular expression that is susceptible to excessive backtracking when attempting to sanitize certain SVG attributes. This may lead to a denial of service through CPU resource consumption. This issue is patched in version 2.19.1.

AI Insight

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

Loofah <2.19.1 uses a slow regex for SVG attribute validation, enabling ReDoS and CPU-based denial of service via crafted input.

Vulnerability

Overview CVE-2022-23514 is a Regular Expression Denial of Service (ReDoS) vulnerability in the Ruby library Loofah, a popular HTML/XML sanitizer built on Nokogiri. Versions prior to 2.19.1 contain an inefficient regular expression used to validate certain SVG attributes. When processing specially crafted SVG content, this regex triggers excessive backtracking, consuming significant CPU resources and potentially causing a denial of service [2]. The root cause was a regex-based attribute check that was replaced with the faster Crass CSS parser in the fix commit [3].

Exploitation

Vector An attacker can exploit this vulnerability by supplying a malicious HTML or XML document containing a crafted SVG element—specifically, a style attribute or similar SVG attribute that triggers the problematic regex. The attack requires no authentication and can be delivered over any channel where the library is used to sanitize user-supplied input, such as web forms, API endpoints, or content management systems. No special network position is needed beyond the ability to submit data to a service that uses the vulnerable Loofah version [1][2].

Impact

Successful exploitation leads to excessive CPU consumption and a denial-of-service condition. The library may become unresponsive or hang while processing the malicious payload, impacting the availability of the host application. There is no evidence of data compromise or privilege escalation, as the vulnerability is limited to resource exhaustion [2].

Mitigation

The vulnerability is patched in Loofah version 2.19.1, released on December 14, 2022. Users should upgrade immediately. No workarounds are documented, and the fix replaces the inefficient regex with a parser-based approach that avoids catastrophic backtracking [3]. The issue is not known to be listed in CISA's Known Exploited Vulnerabilities catalog at this time.

AI Insight generated on May 20, 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
loofahRubyGems
< 2.19.12.19.1

Affected products

8

Patches

1
a6e0a1ab9067

fix: replace slow regex attribute check with crass parser

https://github.com/flavorjones/loofahMike DalessioNov 17, 2022via ghsa
3 files changed · +33 18
  • lib/loofah/html5/scrub.rb+26 1 modified
    @@ -51,9 +51,11 @@ def scrub_attributes(node)
                     end
                   end
                 end
    +
                 if SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
    -              attr_node.value = attr_node.value.gsub(/url\s*\(\s*[^#\s][^)]+?\)/m, " ") if attr_node.value
    +              scrub_attribute_that_allows_local_ref(attr_node)
                 end
    +
                 if SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == "xlink:href" && attr_node.value =~ /^\s*[^#\s].*/m
                   attr_node.remove
                   next
    @@ -127,6 +129,29 @@ def scrub_css(style)
               Crass::Parser.stringify(sanitized_tree)
             end
     
    +        def scrub_attribute_that_allows_local_ref(attr_node)
    +          return unless attr_node.value
    +
    +          nodes = Crass::Parser.new(attr_node.value).parse_component_values
    +
    +          values = nodes.map do |node|
    +            case node[:node]
    +            when :url
    +              if node[:value].start_with?("#")
    +                node[:raw]
    +              else
    +                nil
    +              end
    +            when :hash, :ident, :string
    +              node[:raw]
    +            else
    +              nil
    +            end
    +          end.compact
    +
    +          attr_node.value = values.join(" ")
    +        end
    +
             #
             #  libxml2 >= 2.9.2 fails to escape comments within some attributes.
             #
    
  • test/assets/testdata_sanitizer_tests1.dat+3 3 modified
    @@ -463,9 +463,9 @@
       {
         "name": "absolute_uri_refs_in_svg_attributes",
         "input": "<rect fill='url(http://bad.com/) #fff' />",
    -    "rexml": "<rect fill='  #fff'></rect>",
    -    "xhtml": "<rect fill='  #fff'></rect>",
    -    "output": "<rect fill='  #fff'/>"
    +    "rexml": "<rect fill='#fff'></rect>",
    +    "xhtml": "<rect fill='#fff'></rect>",
    +    "output": "<rect fill='#fff'/>"
       },
     
       {
    
  • test/html5/test_sanitizer.rb+4 14 modified
    @@ -267,15 +267,15 @@ def test_figure_element_is_valid
     
       ## added because we don't have any coverage above on SVG_ATTR_VAL_ALLOWS_REF
       HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.each do |attr_name|
    -    define_method "test_should_allow_uri_refs_in_svg_attribute_#{attr_name}" do
    +    define_method "test_allow_uri_refs_in_svg_attribute_#{attr_name}" do
           input = "<rect fill='url(#foo)' />"
           output = "<rect fill='url(#foo)'></rect>"
           check_sanitization(input, output, output, output)
         end
     
    -    define_method "test_absolute_uri_refs_in_svg_attribute_#{attr_name}" do
    -      input = "<rect fill='url(http://bad.com/) #fff' />"
    -      output = "<rect fill='  #fff'></rect>"
    +    define_method "test_disallow_absolute_uri_refs_in_svg_attribute_#{attr_name}" do
    +      input = "<rect fill='yellow url(http://bad.com/) #fff \"blue\"' />"
    +      output = "<rect fill='yellow #fff \"blue\"'></rect>"
           check_sanitization(input, output, output, output)
         end
       end
    @@ -480,16 +480,6 @@ def test_css_order
         assert_match %r/order:5/, sane.inner_html
       end
     
    -  def test_issue_90_slow_regex
    -    skip("timing tests are hard to make pass and have little regression-testing value")
    -
    -    html = %q{<span style="background: url('data:image/svg&#43;xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22%23D4C8AE%22%20d%3D%22M0%200h32v32h-32z%22%2F%3E%3Cpath%20fill%3D%22%2383604B%22%20d%3D%22M0%200h31.99v11.75h-31.99z%22%2F%3E%3Cpath%20fill%3D%22%233D2319%22%20d%3D%22M0%2011.5h32v.5h-32z%22%2F%3E%3Cpath%20fill%3D%22%23F83651%22%20d%3D%22M5%200h1v10.5h-1z%22%2F%3E%3Cpath%20fill%3D%22%23FCD050%22%20d%3D%22M6%200h1v10.5h-1z%22%2F%3E%3Cpath%20fill%3D%22%2371C797%22%20d%3D%22M7%200h1v10.5h-1z%22%2F%3E%3Cpath%20fill%3D%22%23509CF9%22%20d%3D%22M8%200h1v10.5h-1z%22%2F%3E%3ClinearGradient%20id%3D%22a%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%2224.996%22%20y1%3D%2210.5%22%20x2%3D%2224.996%22%20y2%3D%224.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23796055%22%2F%3E%3Cstop%20offset%3D%22.434%22%20stop-color%3D%22%23614C43%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%233D2D28%22%2F%3E%3C%2FlinearGradient%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M28%208.5c0%201.1-.9%202-2%202h-2c-1.1%200-2-.9-2-2v-2c0-1.1.9-2%202-2h2c1.1%200%202%20.9%202%202v2z%22%2F%3E%3Cpath%20fill%3D%22%235F402E%22%20d%3D%22M28%208c0%201.1-.9%202-2%202h-2c-1.1%200-2-.9-2-2v-2c0-1.1.9-2%202-2h2c1.1%200%202%20.9%202%202v2z%22%2F%3E%3C');"></span>}
    -
    -    assert_completes_in_reasonable_time {
    -      Nokogiri::HTML(Loofah.scrub_fragment(html, :strip).to_html)
    -    }
    -  end
    -
       def test_upper_case_css_property
         html = "<div style=\"COLOR: BLUE; NOTAPROPERTY: RED;\">asdf</div>"
         sane = Nokogiri::HTML(Loofah.scrub_fragment(html, :strip).to_xml)
    

Vulnerability mechanics

Generated on May 9, 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.