Mercurial > hg > isophonics-drupal-site
diff core/tests/Drupal/Tests/Component/Utility/XssTest.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 129ea1e6d783 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,622 @@ +<?php + +namespace Drupal\Tests\Component\Utility; + +use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\UrlHelper; +use Drupal\Component\Utility\Xss; +use PHPUnit\Framework\TestCase; + +/** + * XSS Filtering tests. + * + * @group Utility + * + * @coversDefaultClass \Drupal\Component\Utility\Xss + * + * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html. + * + * Relevant CVEs: + * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973, + * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740. + */ +class XssTest extends TestCase { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $allowed_protocols = [ + 'http', + 'https', + 'ftp', + 'news', + 'nntp', + 'telnet', + 'mailto', + 'irc', + 'ssh', + 'sftp', + 'webcal', + 'rtsp', + ]; + UrlHelper::setAllowedProtocols($allowed_protocols); + } + + /** + * Tests limiting allowed tags and XSS prevention. + * + * XSS tests assume that script is disallowed by default and src is allowed + * by default, but on* and style attributes are disallowed. + * + * @param string $value + * The value to filter. + * @param string $expected + * The expected result. + * @param string $message + * The assertion message to display upon failure. + * @param array $allowed_tags + * (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter(). + * + * @dataProvider providerTestFilterXssNormalized + */ + public function testFilterXssNormalized($value, $expected, $message, array $allowed_tags = NULL) { + if ($allowed_tags === NULL) { + $value = Xss::filter($value); + } + else { + $value = Xss::filter($value, $allowed_tags); + } + $this->assertNormalized($value, $expected, $message); + } + + /** + * Data provider for testFilterXssNormalized(). + * + * @see testFilterXssNormalized() + * + * @return array + * An array of arrays containing strings: + * - The value to filter. + * - The value to expect after filtering. + * - The assertion message. + * - (optional) The allowed HTML HTML tags array that should be passed to + * \Drupal\Component\Utility\Xss::filter(). + */ + public function providerTestFilterXssNormalized() { + return [ + [ + "Who's Online", + "who's online", + 'HTML filter -- html entity number', + ], + [ + "Who&#039;s Online", + "who's online", + 'HTML filter -- encoded html entity number', + ], + [ + "Who&amp;#039; Online", + "who&#039; online", + 'HTML filter -- double encoded html entity number', + ], + // Custom elements with dashes in the tag name. + [ + "<test-element></test-element>", + "<test-element></test-element>", + 'Custom element with dashes in tag name.', + ['test-element'], + ], + ]; + } + + /** + * Tests limiting to allowed tags and XSS prevention. + * + * XSS tests assume that script is disallowed by default and src is allowed + * by default, but on* and style attributes are disallowed. + * + * @param string $value + * The value to filter. + * @param string $expected + * The string that is expected to be missing. + * @param string $message + * The assertion message to display upon failure. + * @param array $allowed_tags + * (optional) The allowed HTML tags to be passed to \Drupal\Component\Utility\Xss::filter(). + * + * @dataProvider providerTestFilterXssNotNormalized + */ + public function testFilterXssNotNormalized($value, $expected, $message, array $allowed_tags = NULL) { + if ($allowed_tags === NULL) { + $value = Xss::filter($value); + } + else { + $value = Xss::filter($value, $allowed_tags); + } + $this->assertNotNormalized($value, $expected, $message); + } + + /** + * Data provider for testFilterXssNotNormalized(). + * + * @see testFilterXssNotNormalized() + * + * @return array + * An array of arrays containing the following elements: + * - The value to filter. + * - The value to expect that's missing after filtering. + * - The assertion message. + * - (optional) The allowed HTML HTML tags array that should be passed to + * \Drupal\Component\Utility\Xss::filter(). + */ + public function providerTestFilterXssNotNormalized() { + $cases = [ + // Tag stripping, different ways to work around removal of HTML tags. + [ + '<script>alert(0)</script>', + 'script', + 'HTML tag stripping -- simple script without special characters.', + ], + [ + '<script src="http://www.example.com" />', + 'script', + 'HTML tag stripping -- empty script with source.', + ], + [ + '<ScRipt sRc=http://www.example.com/>', + 'script', + 'HTML tag stripping evasion -- varying case.', + ], + [ + "<script\nsrc\n=\nhttp://www.example.com/\n>", + 'script', + 'HTML tag stripping evasion -- multiline tag.', + ], + [ + '<script/a src=http://www.example.com/a.js></script>', + 'script', + 'HTML tag stripping evasion -- non whitespace character after tag name.', + ], + [ + '<script/src=http://www.example.com/a.js></script>', + 'script', + 'HTML tag stripping evasion -- no space between tag and attribute.', + ], + // Null between < and tag name works at least with IE6. + [ + "<\0scr\0ipt>alert(0)</script>", + 'ipt', + 'HTML tag stripping evasion -- breaking HTML with nulls.', + ], + [ + "<scrscriptipt src=http://www.example.com/a.js>", + 'script', + 'HTML tag stripping evasion -- filter just removing "script".', + ], + [ + '<<script>alert(0);//<</script>', + 'script', + 'HTML tag stripping evasion -- double opening brackets.', + ], + [ + '<script src=http://www.example.com/a.js?<b>', + 'script', + 'HTML tag stripping evasion -- no closing tag.', + ], + // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should + // work consistently. + [ + '<script>>', + 'script', + 'HTML tag stripping evasion -- double closing tag.', + ], + [ + '<script src=//www.example.com/.a>', + 'script', + 'HTML tag stripping evasion -- no scheme or ending slash.', + ], + [ + '<script src=http://www.example.com/.a', + 'script', + 'HTML tag stripping evasion -- no closing bracket.', + ], + [ + '<script src=http://www.example.com/ <', + 'script', + 'HTML tag stripping evasion -- opening instead of closing bracket.', + ], + [ + '<nosuchtag attribute="newScriptInjectionVector">', + 'nosuchtag', + 'HTML tag stripping evasion -- unknown tag.', + ], + [ + '<t:set attributeName="innerHTML" to="<script defer>alert(0)</script>">', + 't:set', + 'HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).', + ], + [ + '<img """><script>alert(0)</script>', + 'script', + 'HTML tag stripping evasion -- a malformed image tag.', + ['img'], + ], + [ + '<blockquote><script>alert(0)</script></blockquote>', + 'script', + 'HTML tag stripping evasion -- script in a blockqoute.', + ['blockquote'], + ], + [ + "<!--[if true]><script>alert(0)</script><![endif]-->", + 'script', + 'HTML tag stripping evasion -- script within a comment.', + ], + // Dangerous attributes removal. + [ + '<p onmouseover="http://www.example.com/">', + 'onmouseover', + 'HTML filter attributes removal -- events, no evasion.', + ['p'], + ], + [ + '<li style="list-style-image: url(javascript:alert(0))">', + 'style', + 'HTML filter attributes removal -- style, no evasion.', + ['li'], + ], + [ + '<img onerror =alert(0)>', + 'onerror', + 'HTML filter attributes removal evasion -- spaces before equals sign.', + ['img'], + ], + [ + '<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', + 'onabort', + 'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.', + ['img'], + ], + [ + '<img oNmediAError=alert(0)>', + 'onmediaerror', + 'HTML filter attributes removal evasion -- varying case.', + ['img'], + ], + // Works at least with IE6. + [ + "<img o\0nfocus\0=alert(0)>", + 'focus', + 'HTML filter attributes removal evasion -- breaking with nulls.', + ['img'], + ], + // Only whitelisted scheme names allowed in attributes. + [ + '<img src="javascript:alert(0)">', + 'javascript', + 'HTML scheme clearing -- no evasion.', + ['img'], + ], + [ + '<img src=javascript:alert(0)>', + 'javascript', + 'HTML scheme clearing evasion -- no quotes.', + ['img'], + ], + // A bit like CVE-2006-0070. + [ + '<img src="javascript:confirm(0)">', + 'javascript', + 'HTML scheme clearing evasion -- no alert ;)', + ['img'], + ], + [ + '<img src=`javascript:alert(0)`>', + 'javascript', + 'HTML scheme clearing evasion -- grave accents.', + ['img'], + ], + [ + '<img dynsrc="javascript:alert(0)">', + 'javascript', + 'HTML scheme clearing -- rare attribute.', + ['img'], + ], + [ + '<table background="javascript:alert(0)">', + 'javascript', + 'HTML scheme clearing -- another tag.', + ['table'], + ], + [ + '<base href="javascript:alert(0);//">', + 'javascript', + 'HTML scheme clearing -- one more attribute and tag.', + ['base'], + ], + [ + '<img src="jaVaSCriPt:alert(0)">', + 'javascript', + 'HTML scheme clearing evasion -- varying case.', + ['img'], + ], + [ + '<img src=javascript:alert(0)>', + 'javascript', + 'HTML scheme clearing evasion -- UTF-8 decimal encoding.', + ['img'], + ], + [ + '<img src=javascript:alert(0)>', + 'javascript', + 'HTML scheme clearing evasion -- long UTF-8 encoding.', + ['img'], + ], + [ + '<img src=javascript:alert(0)>', + 'javascript', + 'HTML scheme clearing evasion -- UTF-8 hex encoding.', + ['img'], + ], + [ + "<img src=\"jav\tascript:alert(0)\">", + 'script', + 'HTML scheme clearing evasion -- an embedded tab.', + ['img'], + ], + [ + '<img src="jav	ascript:alert(0)">', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded tab.', + ['img'], + ], + [ + '<img src="jav
ascript:alert(0)">', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded newline.', + ['img'], + ], + // With 
 this test would fail, but the entity gets turned into + // &#xD;, so it's OK. + [ + '<img src="jav
ascript:alert(0)">', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded carriage return.', + ['img'], + ], + [ + "<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", + 'cript', + 'HTML scheme clearing evasion -- broken into many lines.', + ['img'], + ], + [ + "<img src=\"jav\0a\0\0cript:alert(0)\">", + 'cript', + 'HTML scheme clearing evasion -- embedded nulls.', + ['img'], + ], + [ + '<img src="vbscript:msgbox(0)">', + 'vbscript', + 'HTML scheme clearing evasion -- another scheme.', + ['img'], + ], + [ + '<img src="nosuchscheme:notice(0)">', + 'nosuchscheme', + 'HTML scheme clearing evasion -- unknown scheme.', + ['img'], + ], + // Netscape 4.x javascript entities. + [ + '<br size="&{alert(0)}">', + 'alert', + 'Netscape 4.x javascript entities.', + ['br'], + ], + // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with + // Internet Explorer 6. + [ + "<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", + 'style', + 'HTML filter -- invalid UTF-8.', + ['p'], + ], + ]; + // @fixme This dataset currently fails under 5.4 because of + // https://www.drupal.org/node/1210798. Restore after its fixed. + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $cases[] = [ + '<img src="  javascript:alert(0)">', + 'javascript', + 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.', + ['img'], + ]; + } + return $cases; + } + + /** + * Checks that invalid multi-byte sequences are rejected. + * + * @param string $value + * The value to filter. + * @param string $expected + * The expected result. + * @param string $message + * The assertion message to display upon failure. + * + * @dataProvider providerTestInvalidMultiByte + */ + public function testInvalidMultiByte($value, $expected, $message) { + $this->assertEquals(Xss::filter($value), $expected, $message); + } + + /** + * Data provider for testInvalidMultiByte(). + * + * @see testInvalidMultiByte() + * + * @return array + * An array of arrays containing strings: + * - The value to filter. + * - The value to expect after filtering. + * - The assertion message. + */ + public function providerTestInvalidMultiByte() { + return [ + ["Foo\xC0barbaz", '', 'Xss::filter() accepted invalid sequence "Foo\xC0barbaz"'], + ["Fooÿñ", "Fooÿñ", 'Xss::filter() rejects valid sequence Fooÿñ"'], + ["\xc0aaa", '', 'HTML filter -- overlong UTF-8 sequences.'], + ]; + } + + /** + * Checks that strings starting with a question sign are correctly processed. + */ + public function testQuestionSign() { + $value = Xss::filter('<?xml:namespace ns="urn:schemas-microsoft-com:time">'); + $this->assertTrue(stripos($value, '<?xml') === FALSE, 'HTML tag stripping evasion -- starting with a question sign (processing instructions).'); + } + + /** + * Check that strings in HTML attributes are correctly processed. + * + * @covers ::attributes + * @dataProvider providerTestAttributes + */ + public function testAttribute($value, $expected, $message, $allowed_tags = NULL) { + $value = Xss::filter($value, $allowed_tags); + $this->assertEquals($expected, $value, $message); + } + + /** + * Data provider for testFilterXssAdminNotNormalized(). + */ + public function providerTestAttributes() { + return [ + [ + '<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">', + '<img src="http://example.com/foo.jpg" title="Example: title" alt="Example: alt">', + 'Image tag with alt and title attribute', + ['img'] + ], + [ + '<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>', + '<a href="https://www.drupal.org/" rel="dc:publisher">Drupal</a>', + 'Link tag with rel attribute', + ['a'] + ], + [ + '<span property="dc:subject">Drupal 8: The best release ever.</span>', + '<span property="dc:subject">Drupal 8: The best release ever.</span>', + 'Span tag with property attribute', + ['span'] + ], + [ + '<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">', + '<img src="http://example.com/foo.jpg" data-caption="Drupal 8: The best release ever.">', + 'Image tag with data attribute', + ['img'] + ], + [ + '<a data-a2a-url="foo"></a>', + '<a data-a2a-url="foo"></a>', + 'Link tag with numeric data attribute', + ['a'] + ], + ]; + } + + /** + * Checks that \Drupal\Component\Utility\Xss::filterAdmin() correctly strips unallowed tags. + */ + public function testFilterXSSAdmin() { + $value = Xss::filterAdmin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />'); + $this->assertEquals($value, '', 'Admin HTML filter -- should never allow some tags.'); + } + + /** + * Tests the loose, admin HTML filter. + * + * @param string $value + * The value to filter. + * @param string $expected + * The expected result. + * @param string $message + * The assertion message to display upon failure. + * + * @dataProvider providerTestFilterXssAdminNotNormalized + */ + public function testFilterXssAdminNotNormalized($value, $expected, $message) { + $this->assertNotNormalized(Xss::filterAdmin($value), $expected, $message); + } + + /** + * Data provider for testFilterXssAdminNotNormalized(). + * + * @see testFilterXssAdminNotNormalized() + * + * @return array + * An array of arrays containing strings: + * - The value to filter. + * - The value to expect after filtering. + * - The assertion message. + */ + public function providerTestFilterXssAdminNotNormalized() { + return [ + // DRUPAL-SA-2008-044 + ['<object />', 'object', 'Admin HTML filter -- should not allow object tag.'], + ['<script />', 'script', 'Admin HTML filter -- should not allow script tag.'], + ]; + } + + /** + * Asserts that a text transformed to lowercase with HTML entities decoded does contain a given string. + * + * Otherwise fails the test with a given message, similar to all the + * SimpleTest assert* functions. + * + * Note that this does not remove nulls, new lines and other characters that + * could be used to obscure a tag or an attribute name. + * + * @param string $haystack + * Text to look in. + * @param string $needle + * Lowercase, plain text to look for. + * @param string $message + * (optional) Message to display if failed. Defaults to an empty string. + * @param string $group + * (optional) The group this message belongs to. Defaults to 'Other'. + */ + protected function assertNormalized($haystack, $needle, $message = '', $group = 'Other') { + $this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) !== FALSE, $message, $group); + } + + /** + * Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string. + * + * Otherwise fails the test with a given message, similar to all the + * SimpleTest assert* functions. + * + * Note that this does not remove nulls, new lines, and other character that + * could be used to obscure a tag or an attribute name. + * + * @param string $haystack + * Text to look in. + * @param string $needle + * Lowercase, plain text to look for. + * @param string $message + * (optional) Message to display if failed. Defaults to an empty string. + * @param string $group + * (optional) The group this message belongs to. Defaults to 'Other'. + */ + protected function assertNotNormalized($haystack, $needle, $message = '', $group = 'Other') { + $this->assertTrue(strpos(strtolower(Html::decodeEntities($haystack)), $needle) === FALSE, $message, $group); + } + +}