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 );
}
}
}