* @package api * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ use EGroupware\Api; /** * check $_REQUEST data for XSS, vars containing script tags are moved to $GLOBALS['egw_unset_vars'] * * @internal * @param array &$var reference of array to check * @param string $name ='' name of the array * @param boolean $log = true Log the results of checking to the error log */ function _check_script_tag(&$var,$name='',$log=true) { static $preg=null; //old: '/<\/?[^>]*\b(iframe|script|javascript|on(before)?(abort|blur|change|click|dblclick|error|focus|keydown|keypress|keyup|load|mousedown|mousemove|mouseout|mouseover|mouseup|reset|select|submit|unload))\b[^>]*>/i'; if (!isset($preg)) $preg = // forbidden tags like iframe or script '/(<(\s*\/)?\s*(iframe|script|object|embed|math|meta)[^a-z0-9]|'. // on* attributes '<[^>]*on(before)?(abort|blur|change|click|dblclick|error|focus|keydown|keypress|keyup|load|mouse(out|enter|leave|over|move|up|wheel|down)'. '|cached|beforeunload|online|offline|open|message|close|animation(start|end|iteration)|transition(start|end|run)|reset'. '|beforeprint|afterprint|composition(start|update|end)|fullscreenchange|fullscreenerror|cut|copy|auxclick|contextmenu'. '|wheel|drag(start|end|enter|over|leave)|drop|loadstart|progress|timeout|loadendreset|select|submit|unload|resize'. '|propertychange|page(hide|show)|scroll|readystatechange|start|popstate|form|input)\s*=|'. // ="javascript:*" diverse javascript attribute value '<[^>]+(href|src|dynsrc|lowsrc|background|style|poster|action)\s*=\s*("|\')?[^"\']*javascript|'. // benavior:url and expression in style attribute '<[^>]+style\s*=\s*("|\')[^>]*(behavior\s*:\s*url|expression)\s*\()/i'; if (is_array($var)) { foreach($var as $key => $val) { if (is_array($val)) { _check_script_tag($var[$key],$name.'['.$key.']'); } elseif(strpos($val, '<') !== false) // speedup: ignore everything without < { if (preg_match($preg,$val)) { // special handling for $_POST[json_data], to decend into it's decoded content, fixing json direct might break json syntax if ($name == '_POST' && $key == 'json_data' && ($json_data = json_decode($val, true))) { _check_script_tag($json_data, $name.'[json_data]'); $_REQUEST[$key] = $var[$key] = json_encode($json_data); continue; } //error_log(__FUNCTION__."(,$name) ${name}[$key] = ".$var[$key]); $GLOBALS['egw_unset_vars'][$name.'['.$key.']'] = $var[$key]; // attempt to clean the thing $var[$key] = $val = Api\Html\HtmLawed::purify($val); // check if we succeeded, if not drop the var anyway, keep the egw_unset_var in any case if (preg_match($preg,$val)) { if($log) { error_log("*** _check_script_tag($name): unset(${name}[$key]) with value $val***"); } unset($var[$key]); } } } } // in case some stupid old code expects the array-pointer to be at the start of the array reset($var); } } foreach(array('_GET','_POST','_REQUEST','HTTP_GET_VARS','HTTP_POST_VARS') as $n => $where) { $pregs = array( 'order' => '/^[a-zA-Z0-9_,]*$/', 'sort' => '/^(ASC|DESC|asc|desc|0|1|2|3|4|5|6|7){0,1}$/', ); foreach(array('order','sort') as $name) { if (isset($GLOBALS[$where][$name]) && !is_array($GLOBALS[$where][$name]) && !preg_match($pregs[$name],$GLOBALS[$where][$name])) { $GLOBALS[$where][$name] = ''; } } // do the check for script-tags only for _GET and _POST or if we found something in _GET and _POST // speeds up the execusion a bit if (isset($GLOBALS[$where]) && is_array($GLOBALS[$where]) && ($n < 2 || isset($GLOBALS['egw_unset_vars']))) { _check_script_tag($GLOBALS[$where],$where); } } //if (is_array($GLOBALS['egw_unset_vars'])) { echo "egw_unset_vars=
".htmlspecialchars(print_r($GLOBALS['egw_unset_vars'],true))."
"; exit; } // $GLOBALS[egw_info][flags][currentapp] and die if it contains something nasty or unexpected if (isset($GLOBALS['egw_info']) && isset($GLOBALS['egw_info']['flags']) && isset($GLOBALS['egw_info']['flags']['currentapp']) && !preg_match('/^[A-Za-z0-9_-]+$/',$GLOBALS['egw_info']['flags']['currentapp'])) { error_log(__FILE__.': '.__LINE__.' Invalid $GLOBALS[egw_info][flags][currentapp]='.array2string($GLOBALS['egw_info']['flags']['currentapp']).', $_SERVER[REQUEST_URI]='.array2string($_SERVER[REQUEST_URI])); die('Invalid $GLOBALS[egw_info][flags][currentapp]!'); } // neutralises register_globals On, which is not used by eGW // some code from the hardend php project: http://www.hardened-php.net/articles/PHPUG-PHP-Sicherheit-Parametermanipulationen.pdf if (ini_get('register_globals')) { function unregister_globals() { // protect against GLOBALS overwrite or setting egw_info if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS']) || isset($_REQUEST['egw_info']) || isset($_FILES['egw_info'])) { die('GLOBALS overwrite detected!!!'); } // unregister all globals $noUnset = array('GLOBALS','_GET','_POST','_COOKIE','_SERVER','_ENV','_FILES','xajax'); foreach(array_unique(array_merge( array_keys($_GET),array_keys($_POST),array_keys($_COOKIE),array_keys($_SERVER),array_keys($_ENV),array_keys($_FILES), isset($_SESSION) && is_array($_SESSION) ? array_keys($_SESSION) : array())) as $k) { if (!in_array($k,$noUnset) && isset($GLOBALS[$k])) { unset($GLOBALS[$k]); } } } unregister_globals(); } /** * Unserialize a php serialized string, but only if it contains NO objects 'O:\d:"' or 'C:\d:"' pattern * * Should be used for all external content, to guard against exploidts. * * PHP 7.0+ can be told not to instanciate any classes (and calling eg. it's destructor). * In fact it instanciates it as __PHP_Incomplete_Class without any methods and therefore disarming threads. * * @param string $str * @return mixed */ function php_safe_unserialize($str) { if (PHP_VERSION >= 7) { return unserialize($str, array('allowed_classes' => false)); } if ((strpos($str, 'O:') !== false || strpos($str, 'C:') !== false) && preg_match('/(^|;|{)[OC]:\d+:"/', $str)) { error_log(__METHOD__."('$str') contains objects --> return NULL"); return null; // null, not false, to not trigger behavior of returning string itself to app code } return unserialize($str); } /** * Unserialize a json or php serialized array * * Used to migrate from PHP serialized database values to json-encoded ones. * * @param string $str string with serialized array * @param boolean $allow_not_serialized =false true: return $str as is, if it is no serialized array * @return array|str|false false if content can not be unserialized (not null like json_decode!) */ function json_php_unserialize($str, $allow_not_serialized=false) { if ((in_array($str[0], array('a', 'i', 's', 'b', 'O', 'C')) && $str[1] == ':' || $str === 'N;') && ($arr = php_safe_unserialize($str)) !== false || $str === 'b:0;') { return $arr; } if (!$allow_not_serialized || $str[0] == '[' || $str[0] == '{' || $str[0] == '"' || $str === 'null' || ($val = json_decode($str, true)) !== null) { // json_decode return null, if it cant decode the content if (isset($val) || ($val = json_decode($str, true)) !== null || $str === 'null') { return $val; } return false; } return $str; }