Moderate severityNVD Advisory· Published Feb 14, 2022· Updated Apr 23, 2025
Cross-site Scripting in svg-sanitizer
CVE-2022-23638
Description
svg-sanitizer is a SVG/XML sanitizer written in PHP. A cross-site scripting vulnerability impacts all users of the svg-sanitizer library prior to version 0.15.0. This issue is fixed in version 0.15.0. There is currently no workaround available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
enshrined/svg-sanitizePackagist | < 0.15.0 | 0.15.0 |
Affected products
1- Range: < 0.15.0
Patches
117e12ba9c288Merge pull request from GHSA-fqx8-v33p-4qcc
6 files changed · +121 −72
src/data/AllowedTags.php+0 −46 modified@@ -29,56 +29,35 @@ public static function getTags() 'article', 'aside', 'audio', - 'b', 'bdi', 'bdo', - 'big', 'blink', - 'blockquote', - 'body', - 'br', 'button', 'canvas', 'caption', - 'center', 'cite', - 'code', 'col', 'colgroup', 'content', 'data', 'datalist', - 'dd', 'decorator', 'del', 'details', 'dfn', 'dir', 'div', - 'dl', - 'dt', 'element', - 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', 'header', 'hgroup', - 'hr', 'html', - 'i', 'image', - 'img', 'input', 'ins', 'kbd', @@ -89,52 +68,27 @@ public static function getTags() 'map', 'mark', 'marquee', - 'menu', - 'menuitem', 'meter', 'nav', - 'nobr', - 'ol', 'optgroup', 'option', 'output', - 'p', - 'pre', 'progress', 'q', 'rp', 'rt', - 'ruby', - 's', 'samp', 'section', 'select', 'shadow', - 'small', 'source', 'spacer', - 'span', - 'strike', - 'strong', 'style', - 'sub', 'summary', - 'sup', - 'table', - 'tbody', - 'td', 'template', 'textarea', - 'tfoot', - 'th', - 'thead', 'time', - 'tr', 'track', - 'tt', - 'u', - 'ul', - 'var', 'video', 'wbr',
src/Sanitizer.php+98 −25 modified@@ -214,13 +214,10 @@ public function sanitize($dirty) $this->elementReferenceResolver->collect(); $elementsToRemove = $this->elementReferenceResolver->getElementsToRemove(); - // Grab all the elements - $allElements = $this->xmlDocument->getElementsByTagName("*"); - // remove doctype after node elements have been analyzed $this->removeDoctype(); // Start the cleaning proccess - $this->startClean($allElements, $elementsToRemove); + $this->startClean($this->xmlDocument->childNodes, $elementsToRemove); // Save cleaned XML to a variable if ($this->removeXMLTag) { @@ -316,33 +313,63 @@ protected function startClean(\DOMNodeList $elements, array $elementsToRemove) continue; } - // If the tag isn't in the whitelist, remove it and continue with next iteration - if (!in_array(strtolower($currentElement->tagName), $this->allowedTags)) { - $currentElement->parentNode->removeChild($currentElement); - $this->xmlIssues[] = array( - 'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'', - 'line' => $currentElement->getLineNo(), - ); - continue; - } - - $this->cleanHrefs($currentElement); - - $this->cleanXlinkHrefs($currentElement); - - $this->cleanAttributesOnWhitelist($currentElement); - - if (strtolower($currentElement->tagName) === 'use') { - if ($this->isUseTagDirty($currentElement) - || $this->isUseTagExceedingThreshold($currentElement) - ) { + if ($currentElement instanceof \DOMElement) { + // If the tag isn't in the whitelist, remove it and continue with next iteration + if (!in_array(strtolower($currentElement->tagName), $this->allowedTags)) { $currentElement->parentNode->removeChild($currentElement); $this->xmlIssues[] = array( - 'message' => 'Suspicious \'' . $currentElement->tagName . '\'', + 'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'', 'line' => $currentElement->getLineNo(), ); continue; } + + $this->cleanHrefs( $currentElement ); + + $this->cleanXlinkHrefs( $currentElement ); + + $this->cleanAttributesOnWhitelist($currentElement); + + if (strtolower($currentElement->tagName) === 'use') { + if ($this->isUseTagDirty($currentElement) + || $this->isUseTagExceedingThreshold($currentElement) + ) { + $currentElement->parentNode->removeChild($currentElement); + $this->xmlIssues[] = array( + 'message' => 'Suspicious \'' . $currentElement->tagName . '\'', + 'line' => $currentElement->getLineNo(), + ); + continue; + } + } + + // Strip out font elements that will break out of foreign content. + if (strtolower($currentElement->tagName) === 'font') { + $breaksOutOfForeignContent = false; + for ($x = $currentElement->attributes->length - 1; $x >= 0; $x--) { + // get attribute name + $attrName = $currentElement->attributes->item( $x )->name; + + if (in_array($attrName, ['face', 'color', 'size'])) { + $breaksOutOfForeignContent = true; + } + } + + if ($breaksOutOfForeignContent) { + $currentElement->parentNode->removeChild($currentElement); + $this->xmlIssues[] = array( + 'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'', + 'line' => $currentElement->getLineNo(), + ); + continue; + } + } + } + + $this->cleanUnsafeNodes($currentElement); + + if ($currentElement->hasChildNodes()) { + $this->startClean($currentElement->childNodes, $elementsToRemove); } } } @@ -627,4 +654,50 @@ public function setUseNestingLimit($limit) { $this->useNestingLimit = (int) $limit; } + + /** + * Determines whether a node is safe or not. + * + * @param \DOMNode $node + * @return bool + */ + protected function isNodeSafe(\DOMNode $node) { + $safeNodes = [ + '#text', + ]; + + if (!in_array($node->nodeName, $safeNodes, true)) { + return false; + } + + return true; + } + + /** + * Remove nodes that are either invalid or malformed. + * + * @param \DOMNode $currentElement The current element. + */ + protected function cleanUnsafeNodes(\DOMNode $currentElement) { + // If the element doesn't have a tagname, remove it and continue with next iteration + if (!property_exists($currentElement, 'tagName')) { + if (!$this->isNodeSafe($currentElement)) { + $currentElement->parentNode->removeChild($currentElement); + $this->xmlIssues[] = array( + 'message' => 'Suspicious node \'' . $currentElement->nodeName . '\'', + 'line' => $currentElement->getLineNo(), + ); + + return; + } + } + + if ( $currentElement->childNodes && $currentElement->childNodes->length > 0 ) { + for ($j = $currentElement->childNodes->length - 1; $j >= 0; $j--) { + /** @var \DOMElement $childElement */ + $childElement = $currentElement->childNodes->item($j); + $this->cleanUnsafeNodes($childElement); + } + } + } }
tests/data/entityClean.svg+1 −1 modified@@ -1,4 +1,4 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg"> - <text x="0" y="20" font-size="20">&lab;</text> + <text x="0" y="20" font-size="20"></text> </svg>
tests/data/htmlClean.svg+2 −0 added@@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 2 2"></svg>
tests/data/htmlTest.svg+7 −0 added@@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 2 2"> + <!--><img src onerror=alert(1)><!--> + <?x ><img src onerror=alert(1)><?x?> + <p/><![CDATA[ ><img src onerror=alert(1)> ]]> + <font face=""/><![CDATA[ ><img src onerror=alert(1)> ]]> +</svg> \ No newline at end of file
tests/SanitizerTest.php+13 −0 modified@@ -294,4 +294,17 @@ public function testLargeUseDOSattacksAreNullified() self::assertXmlStringEqualsXmlString($expected, $cleanData); } + + public function testInvalidNodesAreHandled() + { + $dataDirectory = __DIR__ . '/data'; + $initialData = file_get_contents($dataDirectory . '/htmlTest.svg'); + $expected = file_get_contents($dataDirectory . '/htmlClean.svg'); + + $sanitizer = new Sanitizer(); + $sanitizer->minify(false); + $cleanData = $sanitizer->sanitize($initialData); + + self::assertXmlStringEqualsXmlString($expected, $cleanData); + } }
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
6- github.com/advisories/GHSA-fqx8-v33p-4qccghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-23638ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/enshrined/svg-sanitize/CVE-2022-23638.yamlghsaWEB
- github.com/darylldoyle/svg-sanitizer/commit/17e12ba9c2881caa6b167d0fbea555c11207fbb0ghsax_refsource_MISCWEB
- github.com/darylldoyle/svg-sanitizer/issues/71ghsaWEB
- github.com/darylldoyle/svg-sanitizer/security/advisories/GHSA-fqx8-v33p-4qccghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.