Skip to content

Commit 17e12ba

Browse files
authored
Merge pull request from GHSA-fqx8-v33p-4qcc
Advisory fix 1
2 parents 307b420 + 689e869 commit 17e12ba

6 files changed

Lines changed: 121 additions & 72 deletions

File tree

src/Sanitizer.php

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,10 @@ public function sanitize($dirty)
214214
$this->elementReferenceResolver->collect();
215215
$elementsToRemove = $this->elementReferenceResolver->getElementsToRemove();
216216

217-
// Grab all the elements
218-
$allElements = $this->xmlDocument->getElementsByTagName("*");
219-
220217
// remove doctype after node elements have been analyzed
221218
$this->removeDoctype();
222219
// Start the cleaning proccess
223-
$this->startClean($allElements, $elementsToRemove);
220+
$this->startClean($this->xmlDocument->childNodes, $elementsToRemove);
224221

225222
// Save cleaned XML to a variable
226223
if ($this->removeXMLTag) {
@@ -316,33 +313,63 @@ protected function startClean(\DOMNodeList $elements, array $elementsToRemove)
316313
continue;
317314
}
318315

319-
// If the tag isn't in the whitelist, remove it and continue with next iteration
320-
if (!in_array(strtolower($currentElement->tagName), $this->allowedTags)) {
321-
$currentElement->parentNode->removeChild($currentElement);
322-
$this->xmlIssues[] = array(
323-
'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'',
324-
'line' => $currentElement->getLineNo(),
325-
);
326-
continue;
327-
}
328-
329-
$this->cleanHrefs($currentElement);
330-
331-
$this->cleanXlinkHrefs($currentElement);
332-
333-
$this->cleanAttributesOnWhitelist($currentElement);
334-
335-
if (strtolower($currentElement->tagName) === 'use') {
336-
if ($this->isUseTagDirty($currentElement)
337-
|| $this->isUseTagExceedingThreshold($currentElement)
338-
) {
316+
if ($currentElement instanceof \DOMElement) {
317+
// If the tag isn't in the whitelist, remove it and continue with next iteration
318+
if (!in_array(strtolower($currentElement->tagName), $this->allowedTags)) {
339319
$currentElement->parentNode->removeChild($currentElement);
340320
$this->xmlIssues[] = array(
341-
'message' => 'Suspicious \'' . $currentElement->tagName . '\'',
321+
'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'',
342322
'line' => $currentElement->getLineNo(),
343323
);
344324
continue;
345325
}
326+
327+
$this->cleanHrefs( $currentElement );
328+
329+
$this->cleanXlinkHrefs( $currentElement );
330+
331+
$this->cleanAttributesOnWhitelist($currentElement);
332+
333+
if (strtolower($currentElement->tagName) === 'use') {
334+
if ($this->isUseTagDirty($currentElement)
335+
|| $this->isUseTagExceedingThreshold($currentElement)
336+
) {
337+
$currentElement->parentNode->removeChild($currentElement);
338+
$this->xmlIssues[] = array(
339+
'message' => 'Suspicious \'' . $currentElement->tagName . '\'',
340+
'line' => $currentElement->getLineNo(),
341+
);
342+
continue;
343+
}
344+
}
345+
346+
// Strip out font elements that will break out of foreign content.
347+
if (strtolower($currentElement->tagName) === 'font') {
348+
$breaksOutOfForeignContent = false;
349+
for ($x = $currentElement->attributes->length - 1; $x >= 0; $x--) {
350+
// get attribute name
351+
$attrName = $currentElement->attributes->item( $x )->name;
352+
353+
if (in_array($attrName, ['face', 'color', 'size'])) {
354+
$breaksOutOfForeignContent = true;
355+
}
356+
}
357+
358+
if ($breaksOutOfForeignContent) {
359+
$currentElement->parentNode->removeChild($currentElement);
360+
$this->xmlIssues[] = array(
361+
'message' => 'Suspicious tag \'' . $currentElement->tagName . '\'',
362+
'line' => $currentElement->getLineNo(),
363+
);
364+
continue;
365+
}
366+
}
367+
}
368+
369+
$this->cleanUnsafeNodes($currentElement);
370+
371+
if ($currentElement->hasChildNodes()) {
372+
$this->startClean($currentElement->childNodes, $elementsToRemove);
346373
}
347374
}
348375
}
@@ -627,4 +654,50 @@ public function setUseNestingLimit($limit)
627654
{
628655
$this->useNestingLimit = (int) $limit;
629656
}
657+
658+
/**
659+
* Determines whether a node is safe or not.
660+
*
661+
* @param \DOMNode $node
662+
* @return bool
663+
*/
664+
protected function isNodeSafe(\DOMNode $node) {
665+
$safeNodes = [
666+
'#text',
667+
];
668+
669+
if (!in_array($node->nodeName, $safeNodes, true)) {
670+
return false;
671+
}
672+
673+
return true;
674+
}
675+
676+
/**
677+
* Remove nodes that are either invalid or malformed.
678+
*
679+
* @param \DOMNode $currentElement The current element.
680+
*/
681+
protected function cleanUnsafeNodes(\DOMNode $currentElement) {
682+
// If the element doesn't have a tagname, remove it and continue with next iteration
683+
if (!property_exists($currentElement, 'tagName')) {
684+
if (!$this->isNodeSafe($currentElement)) {
685+
$currentElement->parentNode->removeChild($currentElement);
686+
$this->xmlIssues[] = array(
687+
'message' => 'Suspicious node \'' . $currentElement->nodeName . '\'',
688+
'line' => $currentElement->getLineNo(),
689+
);
690+
691+
return;
692+
}
693+
}
694+
695+
if ( $currentElement->childNodes && $currentElement->childNodes->length > 0 ) {
696+
for ($j = $currentElement->childNodes->length - 1; $j >= 0; $j--) {
697+
/** @var \DOMElement $childElement */
698+
$childElement = $currentElement->childNodes->item($j);
699+
$this->cleanUnsafeNodes($childElement);
700+
}
701+
}
702+
}
630703
}

src/data/AllowedTags.php

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,56 +29,35 @@ public static function getTags()
2929
'article',
3030
'aside',
3131
'audio',
32-
'b',
3332
'bdi',
3433
'bdo',
35-
'big',
3634
'blink',
37-
'blockquote',
38-
'body',
39-
'br',
4035
'button',
4136
'canvas',
4237
'caption',
43-
'center',
4438
'cite',
45-
'code',
4639
'col',
4740
'colgroup',
4841
'content',
4942
'data',
5043
'datalist',
51-
'dd',
5244
'decorator',
5345
'del',
5446
'details',
5547
'dfn',
5648
'dir',
5749
'div',
58-
'dl',
59-
'dt',
6050
'element',
61-
'em',
6251
'fieldset',
6352
'figcaption',
6453
'figure',
6554
'font',
6655
'footer',
6756
'form',
68-
'h1',
69-
'h2',
70-
'h3',
71-
'h4',
72-
'h5',
73-
'h6',
74-
'head',
7557
'header',
7658
'hgroup',
77-
'hr',
7859
'html',
79-
'i',
8060
'image',
81-
'img',
8261
'input',
8362
'ins',
8463
'kbd',
@@ -89,52 +68,27 @@ public static function getTags()
8968
'map',
9069
'mark',
9170
'marquee',
92-
'menu',
93-
'menuitem',
9471
'meter',
9572
'nav',
96-
'nobr',
97-
'ol',
9873
'optgroup',
9974
'option',
10075
'output',
101-
'p',
102-
'pre',
10376
'progress',
10477
'q',
10578
'rp',
10679
'rt',
107-
'ruby',
108-
's',
10980
'samp',
11081
'section',
11182
'select',
11283
'shadow',
113-
'small',
11484
'source',
11585
'spacer',
116-
'span',
117-
'strike',
118-
'strong',
11986
'style',
120-
'sub',
12187
'summary',
122-
'sup',
123-
'table',
124-
'tbody',
125-
'td',
12688
'template',
12789
'textarea',
128-
'tfoot',
129-
'th',
130-
'thead',
13190
'time',
132-
'tr',
13391
'track',
134-
'tt',
135-
'u',
136-
'ul',
137-
'var',
13892
'video',
13993
'wbr',
14094

tests/SanitizerTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,17 @@ public function testLargeUseDOSattacksAreNullified()
294294

295295
self::assertXmlStringEqualsXmlString($expected, $cleanData);
296296
}
297+
298+
public function testInvalidNodesAreHandled()
299+
{
300+
$dataDirectory = __DIR__ . '/data';
301+
$initialData = file_get_contents($dataDirectory . '/htmlTest.svg');
302+
$expected = file_get_contents($dataDirectory . '/htmlClean.svg');
303+
304+
$sanitizer = new Sanitizer();
305+
$sanitizer->minify(false);
306+
$cleanData = $sanitizer->sanitize($initialData);
307+
308+
self::assertXmlStringEqualsXmlString($expected, $cleanData);
309+
}
297310
}

tests/data/entityClean.svg

Lines changed: 1 addition & 1 deletion
Loading

tests/data/htmlClean.svg

Lines changed: 2 additions & 0 deletions
Loading

tests/data/htmlTest.svg

Lines changed: 7 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)