12 KiB
1. 第三方过滤函数与类
目前大多数应用都有一个参数过滤的统一入口,类似于dedecms的代码。
/include/common.inc.php
foreach(Array('_GET','_POST','_COOKIE') as $_request) // line 52
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
_RunMagicQuotes 函数
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中。
// ------------------------- 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)函数进行过滤
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语句进行过滤。
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语句进行逐个字符进行判断:
else {
$len = strlen($sql);
$mark = $clean = '';
for ($i = 0; $i < $len; $i++) {
case '/':
if (empty($mark) && $sql[$i + 1] == '*') {
$mark = '/*';
$clean .= $mark;
$i++;
} elseif ($mark == '/*' && $sql[$i - 1] == '*') {
$mark = '';
$clean .= '*';
}
break;
这段代码的逻辑是:当检查到SQL语句中存在斜杠(/)时,则去判断下一个字符是不是星号(),如果是星号()就把这两个字符拼接起来,即/,然后继续判断下一个字符是不是星号(),如果是星号则再继续拼接起来,得到/**
最后在如下代码中判断是否存在原来拦截规则里面定义的字符,如果存在则拦截SQL语句执行:
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文件中
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事件的敏感字符。
最后拼接正则将这些字符串替换掉。
$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标记比如给这个函数传入 <h1>xxxxx</h1>经过它处理后返回的字符串为 xxxxx。
2.3. 命令执行过滤函数
PHP为了防止系统命令注入的漏洞,提供了 escapeshellcmd()和escapeshellarg()两个函数对参数进行过滤。