Skip to content

Commit

Permalink
Refactor "hackers_attacks" test to use a data provider.
Browse files Browse the repository at this point in the history
This preserves existing skipped tests, but they didn't formerly appear
as skipped, since the test itself jumped over them without reporting.
  • Loading branch information
dmsnell committed Jun 16, 2024
1 parent 62b5032 commit 51766d1
Showing 1 changed file with 127 additions and 52 deletions.
179 changes: 127 additions & 52 deletions tests/phpunit/tests/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -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))//--&gt;"&gt;\'&gt;alert(String.fromCharCode(88,83,83))=&amp;{}', $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))//--&gt;"&gt;\'&gt;alert(String.fromCharCode(88,83,83))=&amp;{}' );
break;

case 'XSS Quick Test':
$this->assertSame( '\'\';!--"=&amp;{()}', $result );
yield $name => array( $name, $code, '\'\';!--"=&amp;{()}' );
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\')");}<A></A>', $result );
yield $name => array( $name, $code, '.XSS{background-image:url("javascript:alert(\'XSS\')");}<A></A>' );
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( '&lt;META HTTP-EQUIV=&quot;Link&quot; Content=&quot;; REL=stylesheet"&gt;', $result );
yield $name => array( $name, $code, '&lt;META HTTP-EQUIV=&quot;Link&quot; Content=&quot;; REL=stylesheet"&gt;' );
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( '&lt;![CDATA[]]&gt;', $result );
yield $name => array( $name, $code, '&lt;![CDATA[]]&gt;' );
break;

case 'XML data island w/comment':
$this->assertSame( "<I><B>&lt;IMG SRC=&quot;javas<!-- -->cript:alert('XSS')\"&gt;</B></I>", $result );
yield $name => array( $name, $code, "<I><B>&lt;IMG SRC=&quot;javas<!-- -->cript:alert('XSS')\"&gt;</B></I>" );
break;

case 'XML HTML+TIME':
$this->assertSame( '&lt;t:set attributeName=&quot;innerHTML&quot; to=&quot;XSSalert(\'XSS\')"&gt;', $result );
yield $name => array( $name, $code, '&lt;t:set attributeName=&quot;innerHTML&quot; to=&quot;XSSalert(\'XSS\')"&gt;' );
break;

case 'Cookie Manipulation':
$this->assertSame( '&lt;META HTTP-EQUIV=&quot;Set-Cookie&quot; Content=&quot;USERID=alert(\'XSS\')"&gt;', $result );
yield $name => array( $name, $code, '&lt;META HTTP-EQUIV=&quot;Set-Cookie&quot; Content=&quot;USERID=alert(\'XSS\')"&gt;' );
break;

case 'SSI':
$this->assertSame( '&lt;!--#exec cmd=&quot;/bin/echo &#039;<!--#exec cmd="/bin/echo \'=http://ha.ckers.org/xss.js&gt;\'"-->', $result );
yield $name => array( $name, $code, '&lt;!--#exec cmd=&quot;/bin/echo &#039;<!--#exec cmd="/bin/echo \'=http://ha.ckers.org/xss.js&gt;\'"-->' );
break;

case 'PHP':
$this->assertSame( '&lt;? echo(&#039;alert("XSS")\'); ?&gt;', $result );
yield $name => array( $name, $code, '&lt;? echo(&#039;alert("XSS")\'); ?&gt;' );
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( '&amp;alert("XSS")', $result );
yield $name => array( $name, $code, '&amp;alert("XSS")' );
break;

case 'No Closing Script Tag':
$this->assertSame( '&lt;SCRIPT SRC=http://ha.ckers.org/xss.js', $result );
yield $name => array( $name, $code, '&lt;SCRIPT SRC=http://ha.ckers.org/xss.js' );
break;

case 'Half-Open HTML/JavaScript':
$this->assertSame( '&lt;IMG SRC=&quot;javascript:alert(&#039;XSS&#039;)&quot;', $result );
yield $name => array( $name, $code, '&lt;IMG SRC=&quot;javascript:alert(&#039;XSS&#039;)&quot;' );
break;

case 'Double open angle brackets':
$this->assertSame( '&lt;IFRAME SRC=http://ha.ckers.org/scriptlet.html &lt;', $result );
yield $name => array( $name, $code, '&lt;IFRAME SRC=http://ha.ckers.org/scriptlet.html &lt;' );
break;

case 'Extraneous Open Brackets':
$this->assertSame( '&lt;alert("XSS");//&lt;', $result );
yield $name => array( $name, $code, '&lt;alert("XSS");//&lt;' );
break;

case 'Malformed IMG Tags':
$this->assertSame( 'alert("XSS")"&gt;', $result );
yield $name => array( $name, $code, 'alert("XSS")"&gt;' );
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"&gt;', $result );
yield $name => array( $name, $code, '" SRC="http://ha.ckers.org/xss.js"&gt;' );
break;

case 'Evade Regex Filter 4':
$this->assertSame( '\'" SRC="http://ha.ckers.org/xss.js"&gt;', $result );
yield $name => array( $name, $code, '\'" SRC="http://ha.ckers.org/xss.js"&gt;' );
break;

case 'Evade Regex Filter 5':
$this->assertSame( '` SRC="http://ha.ckers.org/xss.js"&gt;', $result );
yield $name => array( $name, $code, '` SRC="http://ha.ckers.org/xss.js"&gt;' );
break;

case 'Filter Evasion 1':
$this->assertSame( 'document.write("&lt;SCRI&quot;);PT SRC="http://ha.ckers.org/xss.js"&gt;', $result );
yield $name => array( $name, $code, 'document.write("&lt;SCRI&quot;);PT SRC="http://ha.ckers.org/xss.js"&gt;' );
break;

case 'Filter Evasion 2':
$this->assertSame( '\'&gt;" SRC="http://ha.ckers.org/xss.js"&gt;', $result );
yield $name => array( $name, $code, '\'&gt;" SRC="http://ha.ckers.org/xss.js"&gt;' );
break;

default:
$this->fail( 'KSES failed on ' . $attack->name . ': ' . $result );
yield $name => array( $name, $code, null );
}
}
}
Expand Down

0 comments on commit 51766d1

Please sign in to comment.