static::removeNodes($scriptElems);
// Remove clickable links to JavaScript URI
- $badLinks = $xPath->query('//*[contains(@href, \'javascript:\')]');
+ $badLinks = $xPath->query('//*[' . static::xpathContains('@href', 'javascript:') . ']');
static::removeNodes($badLinks);
// Remove forms with calls to JavaScript URI
- $badForms = $xPath->query('//*[contains(@action, \'javascript:\')] | //*[contains(@formaction, \'javascript:\')]');
+ $badForms = $xPath->query('//*[' . static::xpathContains('@action', 'javascript:') . '] | //*[' . static::xpathContains('@formaction', 'javascript:') . ']');
static::removeNodes($badForms);
// Remove meta tag to prevent external redirects
- $metaTags = $xPath->query('//meta[contains(@content, \'url\')]');
+ $metaTags = $xPath->query('//meta[' . static::xpathContains('@content', 'url') . ']');
static::removeNodes($metaTags);
// Remove data or JavaScript iFrames
- $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
+ $badIframes = $xPath->query('//*[' . static::xpathContains('@src', 'data:') . '] | //*[' . static::xpathContains('@src', 'javascript:') . '] | //*[@srcdoc]');
static::removeNodes($badIframes);
// Remove 'on*' attributes
return $html;
}
+ /**
+ * Create a xpath contains statement with a translation automatically built within
+ * to affectively search in a cases-insensitive manner.
+ */
+ protected static function xpathContains(string $property, string $value): string
+ {
+ $value = strtolower($value);
+ $upperVal = strtoupper($value);
+ return 'contains(translate(' . $property . ', \'' . $upperVal . '\', \'' . $value . '\'), \'' . $value . '\')';
+ }
+
/**
* Removed all of the given DOMNodes.
*/
}
}
- public function test_iframe_js_and_base64_urls_are_removed()
+ public function test_js_and_base64_src_urls_are_removed()
{
$checks = [
'<iframe src="javascript:alert(document.cookie)"></iframe>',
+ '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
+ '<iframe src="JavAScRipT:alert(document.cookie)"></iframe>',
'<iframe SRC=" javascript: alert(document.cookie)"></iframe>',
'<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+ '<iframe src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
'<iframe src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
+ '<img src="javascript:alert(document.cookie)"/>',
+ '<img src="JavAScRipT:alert(document.cookie)"/>',
+ '<img src="JavAScRipT:alert(document.cookie)"/>',
+ '<img SRC=" javascript: alert(document.cookie)"/>',
+ '<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
+ '<img src="DaTa:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
+ '<img src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg=="/>',
'<iframe srcdoc="<script>window.alert(document.cookie)</script>"></iframe>',
+ '<iframe SRCdoc="<script>window.alert(document.cookie)</script>"></iframe>',
+ '<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>',
];
$this->asEditor();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
$pageView->assertElementNotContains('.page-content', '<iframe>');
+ $pageView->assertElementNotContains('.page-content', '<img');
$pageView->assertElementNotContains('.page-content', '</iframe>');
$pageView->assertElementNotContains('.page-content', 'src=');
$pageView->assertElementNotContains('.page-content', 'javascript:');
$checks = [
'<a id="xss" href="javascript:alert(document.cookie)>Click me</a>',
'<a id="xss" href="javascript: alert(document.cookie)>Click me</a>',
+ '<a id="xss" href="JaVaScRiPt: alert(document.cookie)>Click me</a>',
+ '<a id="xss" href=" JaVaScRiPt: alert(document.cookie)>Click me</a>',
];
$this->asEditor();
$pageView = $this->get($page->getUrl());
$pageView->assertStatus(200);
- $pageView->assertElementNotContains('.page-content', '<a id="xss">');
+ $pageView->assertElementNotContains('.page-content', '<a id="xss"');
$pageView->assertElementNotContains('.page-content', 'href=javascript:');
}
}
{
$checks = [
'<form><input id="xss" type=submit formaction=javascript:alert(document.domain) value=Submit><input></form>',
+ '<form ><button id="xss" formaction="JaVaScRiPt:alert(document.domain)">Click me</button></form>',
'<form ><button id="xss" formaction=javascript:alert(document.domain)>Click me</button></form>',
'<form id="xss" action=javascript:alert(document.domain)><input type=submit value=Submit></form>',
+ '<form id="xss" action="JaVaScRiPt:alert(document.domain)"><input type=submit value=Submit></form>',
];
$this->asEditor();
{
$checks = [
'<meta http-equiv="refresh" content="0; url=//external_url">',
+ '<meta http-equiv="refresh" ConTeNt="0; url=//external_url">',
+ '<meta http-equiv="refresh" content="0; UrL=//external_url">',
];
$this->asEditor();
{
$checks = [
'<p onclick="console.log(\'test\')">Hello</p>',
+ '<p OnCliCk="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p>',
'<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>',
'<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></div></div>',
'<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p><div></div>',
'<a a="<img src=1 onerror=\'alert(1)\'> ',
+ '\<a onclick="alert(document.cookie)"\>xss link\</a\>',
];
$this->asEditor();