|
|
|
|
@ -0,0 +1,339 @@
|
|
|
|
|
- [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标记比如给这个函数传入 ```<h1>xxxxx</h1>```经过它处理后返回的字符串为 ```xxxxx```。
|
|
|
|
|
|
|
|
|
|
## 2.3. 命令执行过滤函数
|
|
|
|
|
|
|
|
|
|
PHP为了防止系统命令注入的漏洞,提供了 [escapeshellcmd()](https://www.php.net/manual/zh/function.escapeshellcmd.php)和[escapeshellarg()]()两个函数对参数进行过滤。
|