From 040997fdc4414776bcac06a3cbaac3b26b5e8a64 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 3 Sep 2021 22:34:49 +0100 Subject: [PATCH] Added filter for xlink:href svg xss Simply remove all such attributes --- app/Util/HtmlContentFilter.php | 26 ++++++++++++++++++++------ tests/Entity/PageContentTest.php | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 729b8047475..f3f29ae04e1 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -2,6 +2,7 @@ namespace BookStack\Util; +use DOMAttr; use DOMDocument; use DOMNodeList; use DOMXPath; @@ -43,13 +44,14 @@ public static function removeScripts(string $html): string $badIframes = $xPath->query('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]'); static::removeNodes($badIframes); + // Remove elements with a xlink:href attribute + // Used in SVG but deprecated anyway, so we'll be a bit more heavy-handed here. + $xlinkHrefAttributes = $xPath->query('//@*[contains(name(), \'xlink:href\')]'); + static::removeAttributes($xlinkHrefAttributes); + // Remove 'on*' attributes $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]'); - foreach ($onAttributes as $attr) { - /** @var \DOMAttr $attr */ - $attrName = $attr->nodeName; - $attr->parentNode->removeAttribute($attrName); - } + static::removeAttributes($onAttributes); $html = ''; $topElems = $doc->documentElement->childNodes->item(0)->childNodes; @@ -72,7 +74,7 @@ protected static function xpathContains(string $property, string $value): string } /** - * Removed all of the given DOMNodes. + * Remove all the given DOMNodes. */ protected static function removeNodes(DOMNodeList $nodes): void { @@ -80,4 +82,16 @@ protected static function removeNodes(DOMNodeList $nodes): void $node->parentNode->removeChild($node); } } + + /** + * Remove all the given attribute nodes. + */ + protected static function removeAttributes(DOMNodeList $attrs): void + { + /** @var DOMAttr $attr */ + foreach ($attrs as $attr) { + $attrName = $attr->nodeName; + $attr->parentNode->removeAttribute($attrName); + } + } } diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index 193f8140010..1b2ce2db2f2 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -305,6 +305,28 @@ public function test_page_content_scripts_show_when_configured() $pageView->assertDontSee('abc123abc123'); } + public function test_svg_xlink_hrefs_are_removed() + { + $checks = [ + '', + '' + ]; + + $this->asEditor(); + $page = Page::query()->first(); + + foreach ($checks as $check) { + $page->html = $check; + $page->save(); + + $pageView = $this->get($page->getUrl()); + $pageView->assertStatus(200); + $pageView->assertElementNotContains('.page-content', 'alert'); + $pageView->assertElementNotContains('.page-content', 'xlink:href'); + $pageView->assertElementNotContains('.page-content', 'application/xml'); + } + } + public function test_page_inline_on_attributes_show_if_configured() { $this->asEditor();