CVE-2018-8048
Description
In the Loofah gem through 2.2.0 for Ruby, non-whitelisted HTML attributes may occur in sanitized output by republishing a crafted HTML fragment.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In Loofah gem ≤2.2.0, specially crafted HTML fragments can cause non-whitelisted attributes to appear in sanitized output, leading to XSS.
Vulnerability
Loofah gem through version 2.2.0 fails to properly escape certain attributes when sanitizing HTML fragments, allowing non-whitelisted attributes to persist in output [1][2]. The issue arises from a libxml2 behavior (≥2.9.2) that fails to escape comments within certain attributes (e.g., href, src, action on a and div tags), which can lead to injection of arbitrary attributes when the sanitized output is re-parsed [3][4]. Affected versions: all Loofah versions up to and including 2.2.0.
Exploitation
An attacker can craft an HTML fragment containing a comment within an attribute value, such as <a href='examp<!--" unsafeattr=foo()>-->le.com'>test. When this fragment is sanitized by Loofah's :prune scrubber, the comment is not properly escaped, and upon serialization and re-parsing, the injected attribute (e.g., unsafeattr) may become part of the output [3]. The attacker does not require authentication; exploitation can occur via any application that uses Loofah to sanitize user-supplied HTML and then re-uses the output (e.g., in a browser).
Impact
Successful exploitation could allow an attacker to inject arbitrary HTML attributes into sanitized output, potentially leading to Cross-Site Scripting (XSS) attacks if the output is rendered in a browser [4]. The impact is medium severity (CVSS 6.7) [4]. This could result in information disclosure, session hijacking, or other client-side attacks, depending on the context of the vulnerable application.
Mitigation
The fix was released in Loofah version 2.2.1 on 2018-03-19 [3][4]. Users should upgrade to 2.2.1 or later. There is no known workaround; upgrading is recommended. The fix ensures that attributes are properly URI-escaped to prevent injection via comments [3]. This CVE is not listed on CISA's Known Exploited Vulnerabilities catalog.
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.
| Package | Affected versions | Patched versions |
|---|---|---|
loofahRubyGems | < 2.2.1 | 2.2.1 |
nokogiriRubyGems | < 1.8.3 | 1.8.3 |
Affected products
10- ghsa-coords10 versionspkg:gem/loofahpkg:gem/nokogiripkg:rpm/opensuse/ruby3.2-rubygem-loofah&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/ruby3.2-rubygem-nokogiri&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-loofah&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-nokogiri&distro=openSUSE%20Tumbleweedpkg:rpm/suse/rubygem-loofah&distro=SUSE%20Enterprise%20Storage%204pkg:rpm/suse/rubygem-loofah&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/rubygem-loofah&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rubygem-loofah&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209
< 2.2.1+ 9 more
- (no CPE)range: < 2.2.1
- (no CPE)range: < 1.8.3
- (no CPE)range: < 2.19.1-1.2
- (no CPE)range: < 1.13.9-1.7
- (no CPE)range: < 2.14.0-1.1
- (no CPE)range: < 1.13.3-1.1
- (no CPE)range: < 2.0.2-3.5.1
- (no CPE)range: < 2.0.2-3.5.1
- (no CPE)range: < 2.0.2-3.5.1
- (no CPE)range: < 2.0.2-3.8.1
Patches
1f739cf8eac58tests and fix for CVE-2018-8048
4 files changed · +124 −3
lib/loofah/html5/libxml2_workarounds.rb+26 −0 added@@ -0,0 +1,26 @@ +# coding: utf-8 +require 'set' + +module Loofah + # + # constants related to working around unhelpful libxml2 behavior + # + # ಠ_ಠ + # + module LibxmlWorkarounds + # + # these attributes and qualifying parent tags are determined by the code at: + # + # https://git.gnome.org/browse/libxml2/tree/HTMLtree.c?h=v2.9.2#n714 + # + # see comments about CVE-2018-8048 within the tests for more information + # + BROKEN_ESCAPING_ATTRIBUTES = Set.new %w[ + href + action + src + name + ] + BROKEN_ESCAPING_ATTRIBUTES_QUALIFYING_TAG = {"name" => "a"} + end +end
lib/loofah/html5/scrub.rb+31 −2 modified@@ -1,5 +1,3 @@ -#encoding: US-ASCII - require 'cgi' require 'crass' @@ -65,6 +63,8 @@ def scrub_attributes node node.attribute_nodes.each do |attr_node| node.remove_attribute(attr_node.name) if attr_node.value !~ /[^[:space:]]/ end + + force_correct_attribute_escaping! node end def scrub_css_attribute node @@ -100,6 +100,35 @@ def scrub_css style Crass::Parser.stringify sanitized_tree end + + private + + # + # libxml2 >= 2.9.2 fails to escape comments within some attributes. + # + # see comments about CVE-2018-8048 within the tests for more information + # + def force_correct_attribute_escaping! node + return unless Nokogiri::VersionInfo.instance.libxml2? + + node.attribute_nodes.each do |attr_node| + next unless LibxmlWorkarounds::BROKEN_ESCAPING_ATTRIBUTES.include?(attr_node.name) + + tag_name = LibxmlWorkarounds::BROKEN_ESCAPING_ATTRIBUTES_QUALIFYING_TAG[attr_node.name] + next unless tag_name.nil? || tag_name == node.name + + # + # this block is just like CGI.escape in Ruby 2.4, but + # only encodes space and double-quote, to mimic + # pre-2.9.2 behavior + # + encoding = attr_node.value.encoding + attr_node.value = attr_node.value.gsub(/[ "]/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end.force_encoding(encoding) + end + end + end end end
lib/loofah.rb+1 −0 modified@@ -6,6 +6,7 @@ require 'loofah/elements' require 'loofah/html5/whitelist' +require 'loofah/html5/libxml2_workarounds' require 'loofah/html5/scrub' require 'loofah/scrubber'
test/integration/test_ad_hoc.rb+66 −1 modified@@ -188,6 +188,71 @@ def test_dont_remove_whitespace_between_tags html = "<p>Foo</p>\n<p>Bar</p>" assert_equal "Foo\nBar", Loofah.scrub_document(html, :prune).text end + + # + # tests for CVE-2018-8048 (see https://github.com/flavorjones/loofah/issues/144) + # + # libxml2 >= 2.9.2 fails to escape comments within some attributes. It + # wants to ensure these comments can be treated as "server-side includes", + # but as a result fails to ensure that serialization is well-formed, + # resulting in an opportunity for XSS injection of code into a final + # re-parsed document (presumably in a browser). + # + # we'll test this by parsing the HTML, serializing it, then + # re-parsing it to ensure there isn't any ambiguity in the output + # that might allow code injection into a browser consuming + # "sanitized" output. + # + [ + # + # these tags and attributes are determined by the code at: + # + # https://git.gnome.org/browse/libxml2/tree/HTMLtree.c?h=v2.9.2#n714 + # + {tag: "a", attr: "href"}, + {tag: "div", attr: "href"}, + {tag: "a", attr: "action"}, + {tag: "div", attr: "action"}, + {tag: "a", attr: "src"}, + {tag: "div", attr: "src"}, + {tag: "a", attr: "name"}, + # + # note that div+name is _not_ affected by the libxml2 issue. + # but we test it anyway to ensure our logic isn't modifying + # attributes that don't need modifying. + # + {tag: "div", attr: "name", unescaped: true}, + ].each do |config| + + define_method "test_uri_escaping_of_#{config[:attr]}_attr_in_#{config[:tag]}_tag" do + html = %{<#{config[:tag]} #{config[:attr]}='examp<!--" unsafeattr=foo()>-->le.com'>test</#{config[:tag]}>} + + reparsed = Loofah.fragment(Loofah.fragment(html).scrub!(:prune).to_html) + attributes = reparsed.at_css(config[:tag]).attribute_nodes + + assert_equal [config[:attr]], attributes.collect(&:name) + if Nokogiri::VersionInfo.new.libxml2? + if config[:unescaped] + # + # this attribute was emitted wrapped in single-quotes, so a double quote is A-OK. + # assert that this attribute's serialization is unaffected. + # + assert_equal %{examp<!--" unsafeattr=foo()>-->le.com}, attributes.first.value + else + # + # let's match the behavior in libxml < 2.9.2. + # test that this attribute's serialization is well-formed and sanitized. + # + assert_equal %{examp<!--%22%20unsafeattr=foo()>-->le.com}, attributes.first.value + end + else + # + # yay for consistency in javaland. move along, nothing to see here. + # + assert_equal %{examp<!--%22 unsafeattr=foo()>-->le.com}, attributes.first.value + end + end + + end end end -
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-x7rv-cr6v-4vm4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-8048ghsaADVISORY
- www.debian.org/security/2018/dsa-4171ghsavendor-advisoryx_refsource_DEBIANWEB
- www.openwall.com/lists/oss-security/2018/03/19/5ghsamailing-listx_refsource_MLISTWEB
- github.com/flavorjones/loofah/commit/f739cf8eac5851f328b8044281d6653f74eff116ghsaWEB
- github.com/flavorjones/loofah/issues/144ghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/loofah/CVE-2018-8048.ymlghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/nokogiri/CVE-2018-8048.ymlghsaWEB
- github.com/sparklemotion/nokogiri/pull/1746ghsaWEB
- security.netapp.com/advisory/ntap-20191122-0003ghsaWEB
- security.netapp.com/advisory/ntap-20191122-0003/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.