Fix javascript obfuscating with HTML comments

This commit is contained in:
Laurent Destailleur 2020-11-26 23:37:35 +01:00
parent 19339bb0ea
commit b6640da281
3 changed files with 88 additions and 61 deletions

View File

@ -68,6 +68,8 @@ function testSqlAndScriptInject($val, $type)
// We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char) // We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
// We should use dol_string_nounprintableascii but function is not yet loaded/available // We should use dol_string_nounprintableascii but function is not yet loaded/available
$val = preg_replace('/[\x00-\x1F\x7F]/u', '', $val); // /u operator makes UTF8 valid characters being ignored so are not included into the replace $val = preg_replace('/[\x00-\x1F\x7F]/u', '', $val); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
// We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
$val = preg_replace('/<!--[^>]*-->/', '', $val);
$inj = 0; $inj = 0;
// For SQL Injection (only GET are used to be included into bad escaped SQL requests) // For SQL Injection (only GET are used to be included into bad escaped SQL requests)

View File

@ -77,8 +77,8 @@ class AllTests
$suite = new PHPUnit\Framework\TestSuite('PHPUnit Framework'); $suite = new PHPUnit\Framework\TestSuite('PHPUnit Framework');
//require_once dirname(__FILE__).'/CoreTest.php'; require_once dirname(__FILE__).'/CoreTest.php';
//$suite->addTestSuite('CoreTest'); $suite->addTestSuite('CoreTest');
require_once dirname(__FILE__).'/AdminLibTest.php'; require_once dirname(__FILE__).'/AdminLibTest.php';
$suite->addTestSuite('AdminLibTest'); $suite->addTestSuite('AdminLibTest');
require_once dirname(__FILE__).'/CompanyLibTest.php'; require_once dirname(__FILE__).'/CompanyLibTest.php';

View File

@ -239,78 +239,99 @@ class CoreTest extends PHPUnit\Framework\TestCase
/** /**
* testSqlAndScriptInject * testSqlAndScriptInjectWithPHPUnit
* *
* @return void * @return void
*/ */
public function testSqlAndScriptInject() public function testSqlAndScriptInjectWithPHPUnit()
{ {
global $dolibarr_main_prod;
global $dolibarr_main_url_root;
global $dolibarr_main_data_root;
global $dolibarr_main_document_root;
global $dolibarr_main_data_root_alt;
global $dolibarr_main_document_root_alt;
global $dolibarr_main_db_host;
global $dolibarr_main_db_port;
global $dolibarr_main_db_type;
global $dolibarr_main_db_prefix;
// This is code copied from main.inc.php !!!!!!!!!!!!!!! // This is code copied from main.inc.php !!!!!!!!!!!!!!!
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
/** /**
* Security: SQL Injection and XSS Injection (scripts) protection (Filters on GET, POST, PHP_SELF). * Security: WAF layer for SQL Injection and XSS Injection (scripts) protection (Filters on GET, POST, PHP_SELF).
* *
* @param string $val Value * @param string $val Value brut found int $_GET, $_POST or PHP_SELF
* @param string $type 1=GET, 0=POST, 2=PHP_SELF * @param string $type 1=GET, 0=POST, 2=PHP_SELF, 3=GET without sql reserved keywords (the less tolerant test)
* @return int >0 if there is an injection * @return int >0 if there is an injection, 0 if none
*/ */
function testSqlAndScriptInject($val, $type) function testSqlAndScriptInject($val, $type)
{ {
// phpcs:enable // Decode string first
$inj = 0; // So <svg o&#110;load='console.log(&quot;123&quot;)' become <svg onload='console.log(&quot;123&quot;)'
// For SQL Injection (only GET and POST are used to be included into bad escaped SQL requests) // So "&colon;&apos;" become ":'" (due to ENT_HTML5)
if ($type != 2) $val = html_entity_decode($val, ENT_QUOTES | ENT_HTML5);
{
$inj += preg_match('/delete\s+from/i', $val); // TODO loop to decode until no more thing to decode ?
$inj += preg_match('/create\s+table/i', $val);
$inj += preg_match('/update.+set.+=/i', $val); // We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
$inj += preg_match('/insert\s+into/i', $val); // We should use dol_string_nounprintableascii but function is not yet loaded/available
$inj += preg_match('/select.+from/i', $val); $val = preg_replace('/[\x00-\x1F\x7F]/u', '', $val); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
$inj += preg_match('/union.+select/i', $val); // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
$inj += preg_match('/into\s+(outfile|dumpfile)/i', $val); $val = preg_replace('/<!--[^>]*-->/', '', $val);
$inj += preg_match('/(\.\.%2f)+/i', $val);
} $inj = 0;
// For XSS Injection done by adding javascript with script // For SQL Injection (only GET are used to be included into bad escaped SQL requests)
// This is all cases a browser consider text is javascript: if ($type == 1 || $type == 3)
// When it found '<script', 'javascript:', '<style', 'onload\s=' on body tag, '="&' on a tag size with old browsers {
// All examples on page: http://ha.ckers.org/xss.html#XSScalc $inj += preg_match('/delete\s+from/i', $val);
// More on https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet $inj += preg_match('/create\s+table/i', $val);
$inj += preg_match('/<script/i', $val); $inj += preg_match('/insert\s+into/i', $val);
$inj += preg_match('/<iframe/i', $val); $inj += preg_match('/select\s+from/i', $val);
$inj += preg_match('/Set\.constructor/i', $val); // ECMA script 6 $inj += preg_match('/into\s+(outfile|dumpfile)/i', $val);
if (! defined('NOSTYLECHECK')) $inj += preg_match('/<style/i', $val); $inj += preg_match('/user\s*\(/i', $val); // avoid to use function user() that return current database login
$inj += preg_match('/base[\s]+href/si', $val); $inj += preg_match('/information_schema/i', $val); // avoid to use request that read information_schema database
$inj += preg_match('/<.*onmouse/si', $val); // onmousexxx can be set on img or any html tag like <img title='...' onmouseover=alert(1)> $inj += preg_match('/<svg/i', $val); // <svg can be allowed in POST
$inj += preg_match('/onerror\s*=/i', $val); // onerror can be set on img or any html tag like <img title='...' onerror = alert(1)> }
$inj += preg_match('/onfocus\s*=/i', $val); // onfocus can be set on input text html tag like <input type='text' value='...' onfocus = alert(1)> if ($type == 3)
$inj += preg_match('/onload\s*=/i', $val); // onload can be set on svg tag <svg/onload=alert(1)> or other tag like body <body onload=alert(1)> {
//$inj += preg_match('/on[A-Z][a-z]+\*=/', $val); // To lock event handlers onAbort(), ... $inj += preg_match('/select|update|delete|truncate|replace|group\s+by|concat|count|from|union/i', $val);
$inj += preg_match('/&#58;|&#0000058|&#x3A/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...' }
//if ($type == 1) if ($type != 2) // Not common key strings, so we can check them both on GET and POST
//{ {
$inj += preg_match('/javascript:/i', $val); $inj += preg_match('/updatexml\(/i', $val);
$inj += preg_match('/vbscript:/i', $val); $inj += preg_match('/update.+set.+=/i', $val);
//} $inj += preg_match('/union.+select/i', $val);
// For XSS Injection done by adding javascript closing html tags like with onmousemove, etc... (closing a src or href tag with not cleaned param) $inj += preg_match('/(\.\.%2f)+/i', $val);
if ($type == 1) $inj += preg_match('/"/i', $val); // We refused " in GET parameters value }
if ($type == 2) $inj += preg_match('/[;"]/', $val); // PHP_SELF is a file system path. It can contains spaces. // For XSS Injection done by closing textarea to execute content into a textarea field
return $inj; $inj += preg_match('/<\/textarea/i', $val);
// For XSS Injection done by adding javascript with script
// This is all cases a browser consider text is javascript:
// When it found '<script', 'javascript:', '<style', 'onload\s=' on body tag, '="&' on a tag size with old browsers
// All examples on page: http://ha.ckers.org/xss.html#XSScalc
// More on https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
$inj += preg_match('/<audio/i', $val);
$inj += preg_match('/<embed/i', $val);
$inj += preg_match('/<iframe/i', $val);
$inj += preg_match('/<object/i', $val);
$inj += preg_match('/<script/i', $val);
$inj += preg_match('/Set\.constructor/i', $val); // ECMA script 6
if (!defined('NOSTYLECHECK')) $inj += preg_match('/<style/i', $val);
$inj += preg_match('/base\s+href/si', $val);
$inj += preg_match('/=data:/si', $val);
// List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp
$inj += preg_match('/onmouse([a-z]*)\s*=/i', $val); // onmousexxx can be set on img or any html tag like <img title='...' onmouseover=alert(1)>
$inj += preg_match('/ondrag([a-z]*)\s*=/i', $val); //
$inj += preg_match('/ontouch([a-z]*)\s*=/i', $val); //
$inj += preg_match('/on(abort|afterprint|beforeprint|beforeunload|blur|canplay|canplaythrough|change|click|contextmenu|copy|cut)\s*=/i', $val);
$inj += preg_match('/on(dblclick|drop|durationchange|ended|error|focus|focusin|focusout|hashchange|input|invalid)\s*=/i', $val);
$inj += preg_match('/on(keydown|keypress|keyup|load|loadeddata|loadedmetadata|loadstart|offline|online|pagehide|pageshow)\s*=/i', $val);
$inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|resize|reset|scroll|search|seeking|select|show|stalled|start|submit|suspend)\s*=/i', $val);
$inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting)\s*=/i', $val);
//$inj += preg_match('/on[A-Z][a-z]+\*=/', $val); // To lock event handlers onAbort(), ...
$inj += preg_match('/&#58;|&#0000058|&#x3A/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
$inj += preg_match('/javascript\s*:/i', $val);
$inj += preg_match('/vbscript\s*:/i', $val);
// For XSS Injection done by adding javascript closing html tags like with onmousemove, etc... (closing a src or href tag with not cleaned param)
if ($type == 1) {
$val = str_replace('enclosure="', 'enclosure=X', $val); // We accept enclosure="
$inj += preg_match('/"/i', $val); // We refused " in GET parameters value.
}
if ($type == 2) $inj += preg_match('/[;"]/', $val); // PHP_SELF is a file system path. It can contains spaces.
return $inj;
} }
// Run tests // Run tests
// More on https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet // More on https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
@ -404,5 +425,9 @@ class CoreTest extends PHPUnit\Framework\TestCase
$test='Set.constructor`alert\x281\x29```'; $test='Set.constructor`alert\x281\x29```';
$result=testSqlAndScriptInject($test, 0); $result=testSqlAndScriptInject($test, 0);
$this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject iii'); $this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject iii');
$test="on<!-- ab\nc -->error=alert(1)";
$result=testSqlAndScriptInject($test, 0);
$this->assertGreaterThanOrEqual($expectedresult, $result, 'Error on testSqlAndScriptInject jjj');
} }
} }