9.1.1 discuz SQL 安全过滤类分析
discuz 全称 Crossday Discuz!Board,是康盛创想(北京)科技有限公司(英文简称 Comsenz)推出的一套开源通用的社区论坛软件系统,使用 PHP+MySQL 开发,现已被腾讯收购,由于用户量巨大,discuz 一直是众多安全爱好者重点研究的对象,所以也被公布过不少的安全漏洞。经过数年的沉淀,如今的 discuz 主程序在代码安全方面已经做得比较成熟。
discuz 在专门有一个 SQL 注入过滤类来过滤 SQL 注入请求,不过也出现了多次绕过的情况,下面我们来分析它的这个 SQL 注入过滤的类。
首先我们先看到 discuz 的配置文件/config/config_global.php 中的“CONFIG SECURITY”部分内容,如下:
// ------------------------- CONFIG SECURITY -------------------------- // $_config['security']['authkey'] = '3ca530i1uCe7lRke' ; $_config['security']['urlxssdefend'] = 1 ; $_config['security']['attackevasive'] = '0' ; $_config['security']['querysafe']['status'] = 1 ; // 是否开启 SQL 注入防御 // 以下是过滤规则 $_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' ;
根据笔者的标注(上面加粗代码),我们可以看到 discuz 配置文件中可以设置是否开启 SQL 注入防御,这个选项默认开启,一般不会有管理员去关闭,再往下的内容:
$_config['security']['querysafe']['daction'] 以及 $_config['security']['querysafe']['dnote']
都是 SQL 注入过滤类的过滤规则,规则里包含了常见的注入关键字。
Discuz 执行 SQL 语句之前会调用\source\class\discuz\discuz_database.php 文件 discuz_database_safecheck 类下面的 checkquery($sql)函数进行过滤,我们来跟进这个函数看看,代码如下:
public static function checkquery ( $sql ) {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 语句进行过滤,我们继续跟进_do_query_safe() 函数,代码如下:
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 判断逻辑来选择过滤的方式:
if ( strpos ( $sql , '/' ) === false && strpos ( $sql , '#' ) === false && strpos ( $sql , '-- ' ) === false && strpos ( $sql , '@' ) === false && strpos ( $sql , '`' ) === false ) { $clean = preg_replace ( "/' ( .+ ?) '/s" , '' , $sql ); } else {
这段代码表示当 SQL 语句里存在'/'、#'、'--'、'@'、'`'这些字符时,则直接调用 preg_replace() 函数将单引号(')中间的内容替换为空,这里之前存在一个绕过,只要把 SQL 注入的语句放到单引号中间,则会被替换为空,进行下面再判断的时候已经检测不到 SQL 注入的关键字,导致绕过的出现,在 MySQL 中使用 @`'`代表 null,SQL 语句可以正常执行。
else 条件中是对整段 SQL 语句进行逐个字符进行判断,比如
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' ; } }
国内知名的多款 cms 应用如 dedecms 等,都有使用类似这个过滤类,另外由于应用的基础架构不一样,这个过滤类应用起来的实际效果也各不太一样,discuz 目前做得相对较好。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论