diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 197714a726a66..48ddb2c3ade06 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -337,133 +337,208 @@ public function test_wp_kses_bad_protocol() { } } - public function test_hackers_attacks() { - $xss = simplexml_load_file( DIR_TESTDATA . '/formatting/xssAttacks.xml' ); - foreach ( $xss->attack as $attack ) { - if ( in_array( (string) $attack->name, array( 'Commented-out Block', 'IMG Embedded commands 2', 'US-ASCII encoding', 'OBJECT w/Flash 2', 'Character Encoding Example' ), true ) ) { - continue; - } + /** + * Ensures that a list of known XSS attacks is caught by WordPress. + * + * @dataProvider data_hackers_attacks + * + * @param string $test_name Attack name from XML input file. + * @param string $test_code Test code from XML input file. + * @param string $expected_result How WordPress is expected to sanitize the test code. + */ + public function test_hackers_attacks( $test_name, $test_code, $expected_result ) { + if ( false === $expected_result ) { + $this->markTestSkipped(); + } - $code = (string) $attack->code; + if ( str_starts_with( 'perl', $test_code ) ) { + $pos = strpos( $test_code, '"' ) + 1; + $test_code = substr( $test_code, $pos, strrpos( $test_code, '"' ) - $pos ); + $test_code = str_replace( '\0', "\0", $test_code ); + } - if ( 'See Below' === $code ) { - continue; - } + $result = trim( wp_kses_data( $test_code ) ); - if ( substr( $code, 0, 4 ) === 'perl' ) { - $pos = strpos( $code, '"' ) + 1; - $code = substr( $code, $pos, strrpos( $code, '"' ) - $pos ); - $code = str_replace( '\0', "\0", $code ); - } + if ( in_array( $result, array( '', 'XSS', 'alert("XSS");', "alert('XSS');" ), true ) ) { + $this->markTestSkipped(); + } + + if ( null === $expected_result ) { + $this->fail( "KSES failed on {$test_name}: {$result}" ); + } - $result = trim( wp_kses_data( $code ) ); + $this->assertSame( + $expected_result, + $result, + 'Improperly transformed known XSS input.' + ); + } + + /** + * Data provider. + * + * @return Generator. + */ + public static function data_hackers_attacks() { + $attacks = simplexml_load_file( DIR_TESTDATA . '/formatting/xssAttacks.xml' ); + + $tests_to_skip = array( + 'Character Encoding Example', + 'Commented-out Block', // This only impacts non-HTML-compliant IE<=6. + 'IMG Embedded commands 2', + 'OBJECT w/Flash 2', + 'US-ASCII encoding', + ); + + foreach ( $attacks as $attack ) { + $name = (string) $attack->name; + + if ( in_array( $name, $tests_to_skip, true ) ) { + yield $name => array( $name, '', false ); + continue; + } - if ( in_array( $result, array( '', 'XSS', 'alert("XSS");', "alert('XSS');" ), true ) ) { + $code = (string) $attack->code; + if ( 'See Below' === $code ) { + yield $name => array( $name, '', false ); continue; } - switch ( $attack->name ) { + switch ( $name ) { case 'XSS Locator': - $this->assertSame( '\';alert(String.fromCharCode(88,83,83))//\\\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\\";alert(String.fromCharCode(88,83,83))//-->">\'>alert(String.fromCharCode(88,83,83))=&{}', $result ); + yield $name => array( $name, $code, '\';alert(String.fromCharCode(88,83,83))//\\\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\\";alert(String.fromCharCode(88,83,83))//-->">\'>alert(String.fromCharCode(88,83,83))=&{}' ); break; + case 'XSS Quick Test': - $this->assertSame( '\'\';!--"=&{()}', $result ); + yield $name => array( $name, $code, '\'\';!--"=&{()}' ); break; + case 'SCRIPT w/Alert()': - $this->assertSame( "alert('XSS')", $result ); + yield $name => array( $name, $code, "alert('XSS')" ); break; + case 'SCRIPT w/Char Code': - $this->assertSame( 'alert(String.fromCharCode(88,83,83))', $result ); + yield $name => array( $name, $code, 'alert(String.fromCharCode(88,83,83))' ); break; + case 'IMG STYLE w/expression': - $this->assertSame( 'exp/*', $result ); + yield $name => array( $name, $code, 'exp/*' ); break; + case 'List-style-image': - $this->assertSame( 'li {list-style-image: url("javascript:alert(\'XSS\')");}XSS', $result ); + yield $name => array( $name, $code, 'li {list-style-image: url("javascript:alert(\'XSS\')");}XSS' ); break; + case 'STYLE': - $this->assertSame( "alert('XSS');", $result ); + yield $name => array( $name, $code, "alert('XSS');" ); break; + case 'STYLE w/background-image': - $this->assertSame( '.XSS{background-image:url("javascript:alert(\'XSS\')");}', $result ); + yield $name => array( $name, $code, '.XSS{background-image:url("javascript:alert(\'XSS\')");}' ); break; + case 'STYLE w/background': - $this->assertSame( 'BODY{background:url("javascript:alert(\'XSS\')")}', $result ); + yield $name => array( $name, $code, 'BODY{background:url("javascript:alert(\'XSS\')")}' ); break; + case 'Remote Stylesheet 2': - $this->assertSame( "@import'http://ha.ckers.org/xss.css';", $result ); + yield $name => array( $name, $code, "@import'http://ha.ckers.org/xss.css';" ); break; + case 'Remote Stylesheet 3': - $this->assertSame( '<META HTTP-EQUIV="Link" Content="; REL=stylesheet">', $result ); + yield $name => array( $name, $code, '<META HTTP-EQUIV="Link" Content="; REL=stylesheet">' ); break; + case 'Remote Stylesheet 4': - $this->assertSame( 'BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}', $result ); + yield $name => array( $name, $code, 'BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}' ); break; + case 'XML data island w/CDATA': - $this->assertSame( '<![CDATA[]]>', $result ); + yield $name => array( $name, $code, '<![CDATA[]]>' ); break; + case 'XML data island w/comment': - $this->assertSame( "<IMG SRC="javascript:alert('XSS')\">", $result ); + yield $name => array( $name, $code, "<IMG SRC="javascript:alert('XSS')\">" ); break; + case 'XML HTML+TIME': - $this->assertSame( '<t:set attributeName="innerHTML" to="XSSalert(\'XSS\')">', $result ); + yield $name => array( $name, $code, '<t:set attributeName="innerHTML" to="XSSalert(\'XSS\')">' ); break; + case 'Cookie Manipulation': - $this->assertSame( '<META HTTP-EQUIV="Set-Cookie" Content="USERID=alert(\'XSS\')">', $result ); + yield $name => array( $name, $code, '<META HTTP-EQUIV="Set-Cookie" Content="USERID=alert(\'XSS\')">' ); break; + case 'SSI': - $this->assertSame( '<!--#exec cmd="/bin/echo '', $result ); + yield $name => array( $name, $code, '<!--#exec cmd="/bin/echo '' ); break; + case 'PHP': - $this->assertSame( '<? echo('alert("XSS")\'); ?>', $result ); + yield $name => array( $name, $code, '<? echo('alert("XSS")\'); ?>' ); break; + case 'UTF-7 Encoding': - $this->assertSame( '+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-', $result ); + yield $name => array( $name, $code, '+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-' ); break; + case 'Escaping JavaScript escapes': - $this->assertSame( '\";alert(\'XSS\');//', $result ); + yield $name => array( $name, $code, '\";alert(\'XSS\');//' ); break; + case 'STYLE w/broken up JavaScript': - $this->assertSame( '@im\port\'\ja\vasc\ript:alert("XSS")\';', $result ); + yield $name => array( $name, $code, '@im\port\'\ja\vasc\ript:alert("XSS")\';' ); break; + case 'Null Chars 2': - $this->assertSame( '&alert("XSS")', $result ); + yield $name => array( $name, $code, '&alert("XSS")' ); break; + case 'No Closing Script Tag': - $this->assertSame( '<SCRIPT SRC=http://ha.ckers.org/xss.js', $result ); + yield $name => array( $name, $code, '<SCRIPT SRC=http://ha.ckers.org/xss.js' ); break; + case 'Half-Open HTML/JavaScript': - $this->assertSame( '<IMG SRC="javascript:alert('XSS')"', $result ); + yield $name => array( $name, $code, '<IMG SRC="javascript:alert('XSS')"' ); break; + case 'Double open angle brackets': - $this->assertSame( '<IFRAME SRC=http://ha.ckers.org/scriptlet.html <', $result ); + yield $name => array( $name, $code, '<IFRAME SRC=http://ha.ckers.org/scriptlet.html <' ); break; + case 'Extraneous Open Brackets': - $this->assertSame( '<alert("XSS");//<', $result ); + yield $name => array( $name, $code, '<alert("XSS");//<' ); break; + case 'Malformed IMG Tags': - $this->assertSame( 'alert("XSS")">', $result ); + yield $name => array( $name, $code, 'alert("XSS")">' ); break; + case 'No Quotes/Semicolons': - $this->assertSame( "a=/XSS/\nalert(a.source)", $result ); + yield $name => array( $name, $code, "a=/XSS/\nalert(a.source)" ); break; + case 'Evade Regex Filter 1': - $this->assertSame( '" SRC="http://ha.ckers.org/xss.js">', $result ); + yield $name => array( $name, $code, '" SRC="http://ha.ckers.org/xss.js">' ); break; + case 'Evade Regex Filter 4': - $this->assertSame( '\'" SRC="http://ha.ckers.org/xss.js">', $result ); + yield $name => array( $name, $code, '\'" SRC="http://ha.ckers.org/xss.js">' ); break; + case 'Evade Regex Filter 5': - $this->assertSame( '` SRC="http://ha.ckers.org/xss.js">', $result ); + yield $name => array( $name, $code, '` SRC="http://ha.ckers.org/xss.js">' ); break; + case 'Filter Evasion 1': - $this->assertSame( 'document.write("<SCRI");PT SRC="http://ha.ckers.org/xss.js">', $result ); + yield $name => array( $name, $code, 'document.write("<SCRI");PT SRC="http://ha.ckers.org/xss.js">' ); break; + case 'Filter Evasion 2': - $this->assertSame( '\'>" SRC="http://ha.ckers.org/xss.js">', $result ); + yield $name => array( $name, $code, '\'>" SRC="http://ha.ckers.org/xss.js">' ); break; + default: - $this->fail( 'KSES failed on ' . $attack->name . ': ' . $result ); + yield $name => array( $name, $code, null ); } } }