Inefficient Regular Expression Complexity in Loofah
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.
| Package | Affected versions | Patched versions |
|---|---|---|
loofahRubyGems | < 2.19.1 | 2.19.1 |
Affected products
8- ghsa-coords7 versionspkg:gem/loofahpkg:rpm/opensuse/rubygem-loofah&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/rubygem-loofah&distro=openSUSE%20Tumbleweedpkg:rpm/suse/rubygem-loofah&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP1pkg:rpm/suse/rubygem-loofah&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP2pkg:rpm/suse/rubygem-loofah&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP3pkg:rpm/suse/rubygem-loofah&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP4
< 2.19.1+ 6 more
- (no CPE)range: < 2.19.1
- (no CPE)range: < 2.2.2-150000.4.9.1
- (no CPE)range: < 2.19.1-1.1
- (no CPE)range: < 2.2.2-150000.4.9.1
- (no CPE)range: < 2.2.2-150000.4.9.1
- (no CPE)range: < 2.2.2-150000.4.9.1
- (no CPE)range: < 2.2.2-150000.4.9.1
- flavorjones/loofahv5Range: < 2.19.1
Patches
1a6e0a1ab9067fix: replace slow regex attribute check with crass parser
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+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- github.com/advisories/GHSA-486f-hjj9-9vhhghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-23514ghsaADVISORY
- github.com/flavorjones/loofah/commit/a6e0a1ab90675a17b1b2be189129d94139e4b143ghsaWEB
- github.com/flavorjones/loofah/security/advisories/GHSA-486f-hjj9-9vhhghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/loofah/CVE-2022-23514.ymlghsaWEB
- hackerone.com/reports/1684163ghsax_refsource_MISCWEB
- lists.debian.org/debian-lts-announce/2023/09/msg00011.htmlghsaWEB
- lists.debian.org/debian-lts-announce/2024/09/msg00044.htmlghsaWEB
News mentions
0No linked articles in our index yet.