### 简要描述: 继续绕啊绕啊 ### 详细说明: 首先还是老地方:archive_act.php(611行) ``` function respond_action() { include_once ROOT . '/lib/plugins/pay/' . front::$get['code'] . '.php'; $payclassname = front::$get['code']; $payobj = new $payclassname(); $uri = $_SERVER["REQUEST_URI"]; $__uriget = strstr($uri, '?'); $__uriget = str_replace('?', '', $__uriget); $__uriget = explode('&', $__uriget); $_GET = array(); foreach ($__uriget as $key => $val) { $tmp = explode('=', $val); $_GET[$tmp[0]] = $tmp[1]; if(preg_match('/\'|select|union|"/i', $tmp1)){ exit('非法参数'); } } file_put_contents('logs11.txt', var_export($_GET,true)); $status = $payobj->respond(); if ($status) { echo '<script type="text/javascript">alert("' . lang('已经付款,跳转到订单查询') . '")</script>'; front::refresh(url('archive/orders/oid/' . front::get('subject'), true)); } else { echo '<script type="text/javascript">alert("' . lang('跳转到订单查询') . '")</script>'; front::refresh(url('archive/orders/oid/' . front::get('subject'), true)); } } ```...
### 简要描述: 继续绕啊绕啊 ### 详细说明: 首先还是老地方:archive_act.php(611行) ``` function respond_action() { include_once ROOT . '/lib/plugins/pay/' . front::$get['code'] . '.php'; $payclassname = front::$get['code']; $payobj = new $payclassname(); $uri = $_SERVER["REQUEST_URI"]; $__uriget = strstr($uri, '?'); $__uriget = str_replace('?', '', $__uriget); $__uriget = explode('&', $__uriget); $_GET = array(); foreach ($__uriget as $key => $val) { $tmp = explode('=', $val); $_GET[$tmp[0]] = $tmp[1]; if(preg_match('/\'|select|union|"/i', $tmp1)){ exit('非法参数'); } } file_put_contents('logs11.txt', var_export($_GET,true)); $status = $payobj->respond(); if ($status) { echo '<script type="text/javascript">alert("' . lang('已经付款,跳转到订单查询') . '")</script>'; front::refresh(url('archive/orders/oid/' . front::get('subject'), true)); } else { echo '<script type="text/javascript">alert("' . lang('跳转到订单查询') . '")</script>'; front::refresh(url('archive/orders/oid/' . front::get('subject'), true)); } } ``` $tmp1与$tmp[1]开发人员一时犯糊涂,没看清,导致无效过滤。而且这里get参数进行了重组,从$_SERVER["REQUEST_URI"]分割获取,多此一举,还导致之前的过滤全部无效,这边过滤又失效。 通过front::$get['code'] 可以控制需要加载的pay文件, 再看文件: alipay.php: ``` function respond() { if (!empty($_POST)) { foreach($_POST as $key =>$data) { if(preg_match('/(=|<|>|\')/', $data)){ return false; } $_GET[$key] = $data; } } $payment = pay::get_payment($_GET['code']); $seller_email = rawurldecode($_GET['seller_email']); $order_sn = str_replace($_GET['subject'],'',$_GET['out_trade_no']); $order_sn = trim($order_sn); if (!pay::check_money($order_sn,$_GET['total_fee'])) { return false; } if($_GET['trade_status'] == "WAIT_SELLER_SEND_GOODS"||$_GET['trade_status'] == "TRADE_FINISHED" || $_GET['trade_status'] == "TRADE_SUCCESS") { pay::changeorders($order_sn,$_GET); return true; }else { return false; } } ``` 控制参数trade_status=WAIT_SELLER_SEND_GOODS, 进入pay::changeorders($order_sn,$_GET); ``` public static function changeorders($id,$orderlog) { //file_put_contents('logs.txt', $id); $where=array(); $where['id']=$id; $where['status']=4; //$where['orderlog']=serialize($orderlog); $update=orders::getInstance()->rec_update($where,$id); if($update<1) { exit('改变订单状态出错,请联系管理员'); } } ``` 在这里 $id 就是之前$order_sn,可以直接由get参数控制。 进入这个方法:$update=orders::getInstance()->rec_update($where,$id); ``` function rec_update($row,$where) { $tbname=$this->name; $sql=$this->sql_update($tbname,$row,$where); //echo $sql." "; return $this->query_unbuffered($sql); } ``` 这里程序员又犯糊涂了, rec_update的方法 where变量明显是第二个参数,传入的时候居然$where放到了第一个参数(这个程序员开了吧!),这样$id值就被当做sql语句的条件了。 好吧 开始绕waf: 首先是360的waf: 检测了好多危险函数,更可恶的全局过滤单引号,看到就杀。但是$order_sn 直接被带入到了where后面 根本不需要单引号,不起作用, 在之前的方法中有一个: $order_sn = str_replace($_GET['subject'],'',$_GET['out_trade_no']); 这样利用替换功能,在危险函数中间都插入^, 再把subject设置成^,就可以成功绕过360waf。 接下来在sql语句执行的时候又有一个过滤器: ``` if(preg_match('/(if|select|ascii|from|sleep)/i', $condition)){ //echo $condition; exit('sql inject'); } ``` 由于是update注入,又不能显示错误,sleep被过滤,只能用BENCHMARK。 又过滤了if,只能用or。 get参数又是从querystring中直接过去,空格会被替换成%20,所有只能用/**/替换: 最终的POC:(延时盲注法,稍微改动下) ### 漏洞证明: