WooYun-2013-24984:PHPCMS最新版(V9)SQL注入一枚

漏洞作者: blue认证白帽子

来源:http://www.wooyun.org/bugs/wooyun-2013-024984

简要描述

比较有意思的一个SQL注入点,代码分析是个体力活,唿~

详细说明

存在于在线充值功能,直接上代码分析,建议先看漏洞证明:

/phpcms/phpcms/modules/pay/deposit.php 96行起的pay_recharge方法

...

$trade_sn    = param::get_cookie('trade_sn'); //约110行位置,如果可以控制$trade_sn,即可注入,事实自然是可以的

            $usernote = $_POST['info']['usernote'] ? $_POST['info']['name'].'['.$trade_sn.']'.'-'.new_html_special_chars(trim($_POST['info']['usernote'])) : $_POST['info']['name'].'['.$trade_sn.']';

            $surplus = array(

                    'userid'      => $this->_userid,

                    'username'    => $this->_username,

                    'money'       => trim(floatval($_POST['info']['price'])),

                    'quantity'    => $_POST['quantity'] ? trim(intval($_POST['quantity'])) : 1,

                    'telephone'   => preg_match('/[0-9\-]+/', $_POST['info']['telephone']) ? trim($_POST['info']['telephone']) : '',

                    'contactname' => $_POST['info']['name'] ? trim($_POST['info']['name']).L('recharge') : $this->_username.L('recharge'),

                    'email'       => is_email($_POST['info']['email']) ? trim($_POST['info']['email']) : '',

                    'addtime'      => SYS_TIME,

                    'ip'          => ip(),

                    'pay_type'      => 'recharge',

                    'pay_id'      => $payment['pay_id'],        

                    'payment'     => trim($payment['pay_name']),

                    'ispay'          => '1',

                    'usernote'    => $usernote,

                    'trade_sn'      => $trade_sn,

            );

            $recordid = $this->handle->set_record($surplus); //直到这里,也没有对$trade_sn进行处理吧?接下来看set_record方法

/phpcms/phpcms/modules/pay/classes/pay_deposit.class.php 12行起

    /**

     * 生成流水记录

     * @param unknown_type 

     */

    public function set_record($data){

        $require_items = array('userid','username','email','contactname','telephone','trade_sn','money','quantity','addtime','paytime','usernote','usernote','pay_type','pay_id','payment','ip','status');

        if(is_array($data)) {

            foreach($data as $key=>$item) {

                if(in_array($key,$require_items)) $info[$key] = $item;

            }            

        } else {

            return false;

        }

        $trade_exist = $this->account_db->get_one(array('trade_sn'=>$info['trade_sn']));  //这里

        if($trade_exist) return $trade_exist['id'];

        $this->account_db->insert($info); //还有这里

        return $this->account_db->insert_id();

    }

好了,关键是控制$trade_sn的值,看param::get_cookie和param::set_cookie方法

/phpcms/phpcms/libs/classes/param.class.php

    public static function set_cookie($var, $value = '', $time = 0) {

        $time = $time > 0 ? $time : ($value == '' ? SYS_TIME - 3600 : 0);

        $s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;

        $var = pc_base::load_config('system','cookie_pre').$var;

        $_COOKIE[$var] = $value;

        if (is_array($value)) {

            foreach($value as $k=>$v) {

                setcookie($var.'['.$k.']', sys_auth($v, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);

            }

        } else {

            setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);  //cookie加密了,而且方法很给力,sys_auth是有auth_key的,基本上可以说破解这个值不容易

        }

    }

......

    public static function get_cookie($var, $default = '') {

        $var = pc_base::load_config('system','cookie_pre').$var;

        return isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], 'DECODE') : $default; //这里是解密方法,咱也用不上

    }

看来想自己更改cookie值很难,不知道加密的auth_key值嘛,可是...如果利用一个能set_cookie($value)的点,并且咱们能控制$value呐?这个点自然是有的~

/phpcms/phpcms/modules/attachment/attachments.php 228行起

    public function swfupload_json() {

        $arr['aid'] = intval($_GET['aid']);  //这个不行,intval了

        $arr['src'] = trim($_GET['src']); //这个可以,虽然$_GET会addslashes,但下面的json_encode会帮上忙(此时' => \',这里是一个反斜线 )

        $arr['filename'] = urlencode($_GET['filename']); //这个不行,urlencode了

        $json_str = json_encode($arr); (此时 \' => \\' 这里是两个反斜线)所以单引号可以用了

        $att_arr_exist = param::get_cookie('att_json');

        $att_arr_exist_tmp = explode('||', $att_arr_exist);

        if(is_array($att_arr_exist_tmp) && in_array($json_str, $att_arr_exist_tmp)) {

            return true;

        } else {

            $json_str = $att_arr_exist ? $att_arr_exist.'||'.$json_str : $json_str;

            param::set_cookie('att_json',$json_str); //这里

            return true;            

        }

    }

漏洞证明

1.在COOKIE att_json为空时(当然可以手动清空),访问以下链接生成att_json

http://localhost/test/phpcms/index.php?m=attachment&c=attachments&a=swfupload_json&src=1%27&filename=a%27 (当然,你可以干点别的)

2.提交在线充值时,更改COOKIE trade_sn为att_json的值

修复方案

对$trade_sn进行addslashes