- [1. 第三方过滤函数与类](#1-第三方过滤函数与类) - [1.1. discuz SQL安全过滤类分析](#11-discuz-sql安全过滤类分析) - [1.2. discuz XSS标签过滤函数分析](#12-discuz-xss标签过滤函数分析) - [2. PHP内置过滤函数](#2-php内置过滤函数) - [2.1. SQL注入过滤函数](#21-sql注入过滤函数) - [2.2. XSS注入过滤函数](#22-xss注入过滤函数) - [2.3. 命令执行过滤函数](#23-命令执行过滤函数) # 1. 第三方过滤函数与类 目前大多数应用都有一个参数过滤的统一入口,类似于dedecms的代码。 ```/include/common.inc.php``` ```php foreach(Array('_GET','_POST','_COOKIE') as $_request) // line 52 { foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v); } ``` _RunMagicQuotes 函数 ```php function _RunMagicQuotes(&$svar) { if(!get_magic_quotes_gpc()) { if( is_array($svar) ) { foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v); } else { $svar = addslashes($svar); } } return $svar; } ``` 这里的确使用了 ```addslashes```函数对特定的字符进行转义,但是还不够完善。 ## 1.1. discuz SQL安全过滤类分析 discuz专门有一个SQL注入过滤类来过滤SQL注入请求```/config/config global.php```中。 ```php // ------------------------- CONFIG SECURITY -------------------------- // line 57 $_config['security']['authkey'] = '2e4617iIZ7jIVt61'; $_config['security']['urlxssdefend'] = 1; $_config['security']['attackevasive'] = '0'; $_config['security']['querysafe']['status'] = 1; $_config['security']['querysafe']['dfunction']['0'] = 'load_file'; $_config['security']['querysafe']['dfunction']['1'] = 'hex'; $_config['security']['querysafe']['dfunction']['2'] = 'substring'; $_config['security']['querysafe']['dfunction']['3'] = 'if'; $_config['security']['querysafe']['dfunction']['4'] = 'ord'; $_config['security']['querysafe']['dfunction']['5'] = 'char'; $_config['security']['querysafe']['daction']['0'] = '@'; $_config['security']['querysafe']['daction']['1'] = 'intooutfile'; $_config['security']['querysafe']['daction']['2'] = 'intodumpfile'; $_config['security']['querysafe']['daction']['3'] = 'unionselect'; $_config['security']['querysafe']['daction']['4'] = '(select'; $_config['security']['querysafe']['daction']['5'] = 'unionall'; $_config['security']['querysafe']['daction']['6'] = 'uniondistinct'; $_config['security']['querysafe']['dnote']['0'] = '/*'; $_config['security']['querysafe']['dnote']['1'] = '*/'; $_config['security']['querysafe']['dnote']['2'] = '#'; $_config['security']['querysafe']['dnote']['3'] = '--'; $_config['security']['querysafe']['dnote']['4'] = '"'; $_config['security']['querysafe']['dlikehex'] = 1; $_config['security']['querysafe']['afullnote'] = '0'; ``` - ```$_config['security']['querysafe']['status'] = 1;```设置是否开启SQL注入防御,这个选项默认开启 - ```'daction']```和```['dnote']```都是SQL注入过滤类的过滤规则,规则里包含了常见的注入关键字 - Discuz 执行 SQL 语句之前会调用 \source\class\discuz\discuz_database.php 文件 discuz_database_safecheck类 - 下面的checkquery($sql)函数进行过滤 ```php public static function checkquery($sql) { // line 347 if (self::$config === null) { self::$config = getglobal('config/security/querysafe'); } if (self::$config['status']) { $check = 1; $cmd = strtoupper(substr(trim($sql), 0, 3)); if(isset(self::$checkcmd[$cmd])) { $check = self::_do_query_safe($sql); } elseif(substr($cmd, 0, 2) === '/*') { $check = -1; } if ($check < 1) { throw new DbException('It is not safe to do this query', 0, $sql); } } return true; } ``` 从代码中可以看到,程序首先加载配置文件中的config/security/querysafe,根据 $config['status']判断 SQL 注入防御是否开启,再到 $check = self::_do_query_safe($sql); 可以看到该函数又调用了同类下的_do_query_safe()函数对SQL语句进行过滤。 ```php private static function _do_query_safe($sql) { $sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql); $mark = $clean = ''; if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false && strpos($sql, '@') === false && strpos($sql, '`') === false) { $clean = preg_replace("/'(.+?)'/s", '', $sql); } else { $len = strlen($sql); $mark = $clean = ''; for ($i = 0; $i < $len; $i++) { $str = $sql[$i]; switch ($str) { case '`': if(!$mark) { $mark = '`'; $clean .= $str; } elseif ($mark == '`') { $mark = ''; } break; case '\'': if (!$mark) { $mark = '\''; $clean .= $str; } elseif ($mark == '\'') { $mark = ''; } break; case '/': if (empty($mark) && $sql[$i + 1] == '*') { $mark = '/*'; $clean .= $mark; $i++; } elseif ($mark == '/*' && $sql[$i - 1] == '*') { $mark = ''; $clean .= '*'; } break; case '#': if (empty($mark)) { $mark = $str; $clean .= $str; } break; case "\n": if ($mark == '#' || $mark == '--') { $mark = ''; } break; case '-': if (empty($mark) && substr($sql, $i, 3) == '-- ') { $mark = '-- '; $clean .= $mark; } break; default: break; } $clean .= $mark ? '' : $str; } } if(strpos($clean, '@') !== false) { return '-3'; } $clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", "", strtolower($clean)); if (self::$config['afullnote']) { $clean = str_replace('/**/', '', $clean); } if (is_array(self::$config['dfunction'])) { foreach (self::$config['dfunction'] as $fun) { if (strpos($clean, $fun . '(') !== false) return '-1'; } } if (is_array(self::$config['daction'])) { foreach (self::$config['daction'] as $action) { if (strpos($clean, $action) !== false) return '-3'; } } if (self::$config['dlikehex'] && strpos($clean, 'like0x')) { return '-2'; } if (is_array(self::$config['dnote'])) { foreach (self::$config['dnote'] as $note) { if (strpos($clean, $note) !== false) return '-4'; } } return 1; } ``` 该函数首先使用```$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);```将SQL语句中的\\、\'、\"以及'、"替换为空紧接着是一个if else判断逻辑来选择过滤的方式: ```$clean = preg_replace("/'(.+?)'/s", '', $sql);``` - 这段代码表示当SQL语句里存在'/'、'#'、'-- '、'@'、'`'这些字符时,则直接调用 preg_replace()函数将单引号(')中间的内容替换为空 - 正则表达式中?匹配前面的子表达式零次或一次。 - /s:默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后,圆点 . 中包含换行符 \n。 else条件中是对整段SQL语句进行逐个字符进行判断: ```php else { $len = strlen($sql); $mark = $clean = ''; for ($i = 0; $i < $len; $i++) { ``` ```php case '/': if (empty($mark) && $sql[$i + 1] == '*') { $mark = '/*'; $clean .= $mark; $i++; } elseif ($mark == '/*' && $sql[$i - 1] == '*') { $mark = ''; $clean .= '*'; } break; ``` 这段代码的逻辑是:当检查到SQL语句中存在斜杠(/)时,则去判断下一个字符是不是星号(*),如果是星号(*)就把这两个字符拼接起来,即/*,然后继续判断下一个字符是不是星号(*),如果是星号则再继续拼接起来,得到/** 最后在如下代码中判断是否存在原来拦截规则里面定义的字符,如果存在则拦截SQL语句执行: ```php if (is_array(self::$config['dnote'])) { foreach (self::$config['dnote'] as $note) { if (strpos($clean, $note) !== false) return '-4'; } } ``` ## 1.2. discuz XSS标签过滤函数分析 在XSS的防御上,只要过滤掉尖括号以及单、 双引号就能干掉绝大部分的payload。 下面是discuz的HTML标签过滤代码在/source/function/function_blog.php文件中 ```php function checkhtml($html) { // line 271 if(!checkperm('allowhtml')) { preg_match_all("/\<([^\<]+)\>/is", $html, $ms); $searchs[] = '<'; $replaces[] = '<'; $searchs[] = '>'; $replaces[] = '>'; if($ms[1]) { $allowtags = 'img|a|font|div|table|tbody|caption|tr|td|th|br|p|b|strong|i|u|em|span|ol|ul|li|blockquote'; $ms[1] = array_unique($ms[1]); foreach ($ms[1] as $value) { $searchs[] = "<".$value.">"; $value = str_replace('&', '_uch_tmp_str_', $value); $value = dhtmlspecialchars($value); $value = str_replace('_uch_tmp_str_', '&', $value); $value = str_replace(array('\\','/*'), array('.','/.'), $value); $skipkeys = array('onabort','onactivate','onafterprint','onafterupdate','onbeforeactivate','onbeforecopy','onbeforecut','onbeforedeactivate', 'onbeforeeditfocus','onbeforepaste','onbeforeprint','onbeforeunload','onbeforeupdate','onblur','onbounce','oncellchange','onchange', 'onclick','oncontextmenu','oncontrolselect','oncopy','oncut','ondataavailable','ondatasetchanged','ondatasetcomplete','ondblclick', 'ondeactivate','ondrag','ondragend','ondragenter','ondragleave','ondragover','ondragstart','ondrop','onerror','onerrorupdate', 'onfilterchange','onfinish','onfocus','onfocusin','onfocusout','onhelp','onkeydown','onkeypress','onkeyup','onlayoutcomplete', 'onload','onlosecapture','onmousedown','onmouseenter','onmouseleave','onmousemove','onmouseout','onmouseover','onmouseup','onmousewheel', 'onmove','onmoveend','onmovestart','onpaste','onpropertychange','onreadystatechange','onreset','onresize','onresizeend','onresizestart', 'onrowenter','onrowexit','onrowsdelete','onrowsinserted','onscroll','onselect','onselectionchange','onselectstart','onstart','onstop', 'onsubmit','onunload','javascript','script','eval','behaviour','expression','style','class'); $skipstr = implode('|', $skipkeys); $value = preg_replace(array("/($skipstr)/i"), '.', $value); if(!preg_match("/^[\/|\s]?($allowtags)(\s+|$)/is", $value)) { $value = ''; } $replaces[] = empty($value)?'':"<".str_replace('"', '"', $value).">"; } } $html = str_replace($searchs, $replaces, $html); } return $html; } ``` ```preg_match_all("/\<([^\<]+)\>/is", $html, $ms);```正则取岀来尖括号中间的内容然后在if($ms[l])这个if条件里对这些标签中的关键字进行筛选。 ```$skipkeys```变量定义了很多on事件的敏感字符。 最后拼接正则将这些字符串替换掉。 ```php $skipstr = implode('|', $skipkeys); $value = preg_replace(array("/($skipstr)/i"), '.', $value); ``` # 2. PHP内置过滤函数 ## 2.1. SQL注入过滤函数 - SQL 注入过滤函数有 addslashes()、mysql_real_escape_string()以及 mysql_escape_string(), - 它们的作用都是给字符串添加反斜杠(\)来转义掉单引号(')、双引号('')、反斜线(\)以及空字符 NULL。 ## 2.2. XSS注入过滤函数 htmlspecialchars()函数的作用是将字符串中的特殊字符转换成HTML实体编码, - 如```&``` 转换成```&``` - ```"``` 转换成```"``` - ```'``` 转换成```'``` - ```<``` 转换成```<``` - ```>``` 转换成```>``` strip_tags()函数则是用来去掉HTML及PHP标记比如给这个函数传入 ```

xxxxx

```经过它处理后返回的字符串为 ```xxxxx```。 ## 2.3. 命令执行过滤函数 PHP为了防止系统命令注入的漏洞,提供了 [escapeshellcmd()](https://www.php.net/manual/zh/function.escapeshellcmd.php)和[escapeshellarg()]()两个函数对参数进行过滤。