diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php index 8b6434b..be3c575 100644 --- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php +++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php @@ -15,6 +15,12 @@ /** * Tests the Xss utility. * + * 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. + * * @see \Drupal\Component\Utility\Xss */ class XssTest extends UnitTestCase { @@ -56,191 +62,414 @@ protected function setUp() { * XSS tests assume that script is disallowed by default and src is allowed * by default, but on* and style attributes are disallowed. * - * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html. + * @param string $value + * The value to filter. + * @param string $expected + * The expected result. + * @param string $message + * The assertion message to display upon failure. * - * 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. + * @dataProvider providerTestFilterXssNormalized */ - public function testFilterXSS() { - // Tag stripping, different ways to work around removal of HTML tags. - $f = Xss::filter(''); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping -- simple script without special characters.'); - - $f = Xss::filter(''); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- non whitespace character after tag name.'); - - $f = Xss::filter(''); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- no space between tag and attribute.'); - - // Null between < and tag name works at least with IE6. - $f = Xss::filter("<\0scr\0ipt>alert(0)"); - $this->assertNoNormalized($f, 'ipt', 'HTML tag stripping evasion -- breaking HTML with nulls.'); - - $f = Xss::filter(""); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- filter just removing "script".'); - - $f = Xss::filter('<'); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- double opening brackets.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- a malformed image tag.'); - - $f = Xss::filter('
', array('blockquote')); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script in a blockqoute.'); - - $f = Xss::filter(""); - $this->assertNoNormalized($f, 'script', 'HTML tag stripping evasion -- script within a comment.'); - - // Dangerous attributes removal. - $f = Xss::filter('

', array('p')); - $this->assertNoNormalized($f, 'onmouseover', 'HTML filter attributes removal -- events, no evasion.'); - - $f = Xss::filter('

  • ', array('li')); - $this->assertNoNormalized($f, 'style', 'HTML filter attributes removal -- style, no evasion.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'onerror', 'HTML filter attributes removal evasion -- spaces before equals sign.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'onabort', 'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'onmediaerror', 'HTML filter attributes removal evasion -- varying case.'); - - // Works at least with IE6. - $f = Xss::filter("", array('img')); - $this->assertNoNormalized($f, 'focus', 'HTML filter attributes removal evasion -- breaking with nulls.'); - - // Only whitelisted scheme names allowed in attributes. - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- no evasion.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no quotes.'); - - // A bit like CVE-2006-0070. - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- no alert ;)'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- grave accents.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- rare attribute.'); - - $f = Xss::filter('', array('table')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- another tag.'); - - $f = Xss::filter('', array('base')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing -- one more attribute and tag.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- varying case.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- UTF-8 decimal encoding.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- long UTF-8 encoding.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- UTF-8 hex encoding.'); - - $f = Xss::filter("", array('img')); - $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an embedded tab.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded tab.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded newline.'); - - // With this test would fail, but the entity gets turned into - // &#xD;, so it's OK. - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'script', 'HTML scheme clearing evasion -- an encoded, embedded carriage return.'); - - $f = Xss::filter("", array('img')); - $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- broken into many lines.'); - - $f = Xss::filter("", array('img')); - $this->assertNoNormalized($f, 'cript', 'HTML scheme clearing evasion -- embedded nulls.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'javascript', 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'vbscript', 'HTML scheme clearing evasion -- another scheme.'); - - $f = Xss::filter('', array('img')); - $this->assertNoNormalized($f, 'nosuchscheme', 'HTML scheme clearing evasion -- unknown scheme.'); - - // Netscape 4.x javascript entities. - $f = Xss::filter('
    ', array('br')); - $this->assertNoNormalized($f, 'alert', 'Netscape 4.x javascript entities.'); + public function testFilterXssNormalized($value, $expected, $message) { + $this->assertNormalized($expected, Xss::filter($value), $message); + } - // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with - // Internet Explorer 6. - $f = Xss::filter("

    \" style=\"background-image: url(javascript:alert(0));\"\xe0

    ", array('p')); - $this->assertNoNormalized($f, 'style', 'HTML filter -- invalid UTF-8.'); + /** + * 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. + */ + public function providerTestFilterXssNormalized() { + return array( + array( + "\xc0aaa", + '', + 'HTML filter -- overlong UTF-8 sequences.', + ), + array( + "Who's Online", + "who's online", + 'HTML filter -- html entity number', + ), + array( + "Who&#039;s Online", + "who's online", + 'HTML filter -- encoded html entity number', + ), + ); + } - $f = Xss::filter("\xc0aaa"); - $this->assertEquals($f, '', 'HTML filter -- overlong UTF-8 sequences.'); + /** + * 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 tags to be passed on 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($expected, $value, $message); + } - $f = Xss::filter("Who's Online"); - $this->assertNormalized($f, "who's online", 'HTML filter -- html entity number'); + /** + * Data provider for testFilterXssNotNormalized(). + * + * @see testFilterXssNotNormalized() + * + * @return array + * An array of arrays containing the following elements: + * - The value to filter string. + * - The value to expect after filtering string. + * - The assertion message string. + * - (optional) The allowed html tags array that should be passed to + * Xss::filter(). + */ + public function providerTestFilterXssNotNormalized() { + return array( + // Tag stripping, different ways to work around removal of HTML tags. + array( + '', + 'script', + 'HTML tag stripping -- simple script without special characters.', + ), + array( + '', + 'script', + 'HTML tag stripping evasion -- non whitespace character after tag name.', + ), + array( + '', + 'script', + 'HTML tag stripping evasion -- no space between tag and attribute.', + ), + // Null between < and tag name works at least with IE6. + array( + "<\0scr\0ipt>alert(0)", + 'ipt', + 'HTML tag stripping evasion -- breaking HTML with nulls.', + ), + array( + "", + 'script', + 'HTML tag stripping evasion -- filter just removing "script".', + ), + array( + '<', + 'script', + 'HTML tag stripping evasion -- double opening brackets.', + ), + array( + '', + 'script', + 'HTML tag stripping evasion -- a malformed image tag.', + array('img'), + ), + array( + '

    ', + 'script', + 'HTML tag stripping evasion -- script in a blockqoute.', + array('blockquote'), + ), + array( + "", + 'script', + 'HTML tag stripping evasion -- script within a comment.', + ), + // Dangerous attributes removal. + array( + '

    ', + 'onmouseover', + 'HTML filter attributes removal -- events, no evasion.', + array('p'), + ), + array( + '

  • ', + 'style', + 'HTML filter attributes removal -- style, no evasion.', + array('li'), + ), + array( + '', + 'onerror', + 'HTML filter attributes removal evasion -- spaces before equals sign.', + array('img'), + ), + array( + '', + 'onabort', + 'HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.', + array('img'), + ), + array( + '', + 'onmediaerror', + 'HTML filter attributes removal evasion -- varying case.', + array('img'), + ), + // Works at least with IE6. + array( + "", + 'focus', + 'HTML filter attributes removal evasion -- breaking with nulls.', + array('img'), + ), + // Only whitelisted scheme names allowed in attributes. + array( + '', + 'javascript', + 'HTML scheme clearing -- no evasion.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- no quotes.', + array('img'), + ), + // A bit like CVE-2006-0070. + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- no alert ;)', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- grave accents.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing -- rare attribute.', + array('img'), + ), + array( + '
  • ', + 'javascript', + 'HTML scheme clearing -- another tag.', + array('table'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing -- one more attribute and tag.', + array('base'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- varying case.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- UTF-8 decimal encoding.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- long UTF-8 encoding.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- UTF-8 hex encoding.', + array('img'), + ), + array( + "", + 'script', + 'HTML scheme clearing evasion -- an embedded tab.', + array('img'), + ), + array( + '', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded tab.', + array('img'), + ), + array( + '', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded newline.', + array('img'), + ), + // With this test would fail, but the entity gets turned into + // &#xD;, so it's OK. + array( + '', + 'script', + 'HTML scheme clearing evasion -- an encoded, embedded carriage return.', + array('img'), + ), + array( + "", + 'cript', + 'HTML scheme clearing evasion -- broken into many lines.', + array('img'), + ), + array( + "", + 'cript', + 'HTML scheme clearing evasion -- embedded nulls.', + array('img'), + ), + array( + '', + 'javascript', + 'HTML scheme clearing evasion -- spaces and metacharacters before scheme.', + array('img'), + ), + array( + '', + 'vbscript', + 'HTML scheme clearing evasion -- another scheme.', + array('img'), + ), + array( + '', + 'nosuchscheme', + 'HTML scheme clearing evasion -- unknown scheme.', + array('img'), + ), + // Netscape 4.x javascript entities. + array( + '
    ', + 'alert', + 'Netscape 4.x javascript entities.', + array('br'), + ), + // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with + // Internet Explorer 6. + array( + "

    \" style=\"background-image: url(javascript:alert(0));\"\xe0

    ", + 'style', + 'HTML filter -- invalid UTF-8.', + array('p'), + ), + ); + } - $f = Xss::filter("Who&#039;s Online"); - $this->assertNormalized($f, "who's online", 'HTML filter -- encoded html entity number'); + /** + * 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($expected, Xss::filter($value), $message); + } - $f = Xss::filter("Who&amp;#039; Online"); - $this->assertNormalized($f, "who&#039; online", 'HTML filter -- double encoded html entity number'); + /** + * 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 array( + array("Foo\xC0barbaz", '', 'Xss::filter() accepted invalid sequence "Foo\xC0barbaz"'), + array("Fooÿñ", "Fooÿñ", 'Xss::filter() rejects valid sequence Fooÿñ"'), + array("\xc0aaa", '', 'HTML filter -- overlong UTF-8 sequences.'), + ); } /** - * Checks that invalid multi-byte sequences are rejected. + * Checks that strings starting with a question sign are correctly processed. */ - public function testInvalidMultiByte() { - $text = Xss::filter("Foo\xC0barbaz"); - $this->assertEquals($text, '', 'filter_xss() accepted invalid sequence "Foo\xC0barbaz"'); - $text = Xss::filter("Fooÿñ"); - $this->assertEquals($text, "Fooÿñ", 'filter_xss() rejects valid sequence Fooÿñ'); + public function testQuestionSign() { + $value = Xss::filter(''); + $this->assertTrue(stripos($value, 'assertTrue(strpos(strtolower(String::decodeEntities($haystack)), $needle) === FALSE, $message, $group); }