前言
從freebuf搬來的(是原作者,勿噴)這篇文章是我在 前幾個月寫的,然后如今才打算發布的,咳咳,也沒啥可以寫的,就寫一下變量覆蓋的形成,如何代碼審計查找白盒專屬的變量覆蓋漏洞。
變量覆蓋是有啥用處?
變量覆蓋可以使用我們自定義的變量去覆蓋 源代碼中的變量,去修改代碼運行的邏輯。變量覆蓋與其他漏洞結合后 傷害是比較大的,比如商品購買的支付系統如果存在變量覆蓋的話可能出現0元支付下單的情況,或者說 登錄管理員后台的時候,通過變量覆蓋,進行登錄后台(這篇文章講解的就是duomicms的變量覆蓋進入后台,小白也很容易懂)。
正文
變量覆蓋產生原因引發變量覆蓋漏洞函數:
extract()
parse_str()
Import_request_variables()
$$(雙美元符)
Register_globals=On (PHP 5.4之后移除)
0x01. extract() 函數
這時候看一下菜鳥教程是怎么使用的
看下語法是 extract(array,value1,value2)我們發現第一個參數是必選值,然后就不必說了
這時候我們發現,其實關鍵的是第二個參數值
EXTR_OVERWRITE - 默認。如果有沖突,則覆蓋已有的變量。
並且發現這里是默認的,這時候就穩妥了,這就導致可以變量覆蓋了
<?php
$user = 'user';
echo "歡迎用戶 $user !<br>";
$admin = array('user' => 'admin');
extract($admin);
echo "歡迎管理員 $user ";?>
看下這個代碼邏輯,變量user 等於 字符串user然后輸出這個變量 user之后我們再定義一個變量admin然后就是定義數組,鍵為 字符串 user,值為admin然后通過extract() 這個函數,里面就是數組
array(‘user’ => ‘admin’)
然后是默認 EXTR_OVERWRITE 這個參數,這時候發現鍵名和上面的變量名重復於是就會覆蓋掉原本那個 user
0x02.parse_str()函數
老規矩,看下是怎么解釋的,這里說,如果沒有設置array參數那么函數設置的變量將會覆蓋已經存在的 同名變量然后我們看下面這個 array這個參數規定存儲變量的數組的名稱。該參數指示變量將被存儲到數組中所以也就是說,我們只要沒有array這個參數,就會導致變量覆蓋這個漏洞
<?php
$username = 'user';
print_r('執行函數覆蓋之前:$username ='.$username."<br>");
// 執行函數 parse_str() ,然后里面沒有數組成功覆蓋
parse_str("username=admin");
print_r('執行函數覆蓋之后:$username ='.$username);
?>
0x03. import_request_variables()函數
這里我們簡單的看下type參數就行,其實這里就是說P = post g = get c = cookie然后就可以理解了,從左到右的順序這些順序就是優先級,也就是說 gpc,意思就是說,get傳參會覆蓋 post傳參,而post傳參,則覆蓋cookie傳參,同理可得: get傳參也可以覆蓋cookie
差不多就是這個意思了
0x04. $$雙美元符導致的變量覆蓋
<?php
$a = 'b';
$b= 'admin';
echo $$a;
?>
這里源碼我寫的比較粗糙,哥哥們看的懂就行輸出的結果為 admin,而接下來講的就是$$導致的變量覆蓋
0x05.實戰講解
先安裝好這個cms,然后再審計
漏洞地址在 upload/duomiphp/common.php
我們直接進入我們的$$符查找,奈何技術太菜,沒法全文通讀這在我的博客上面也有提到過,鍵值分離
先把其他的注釋掉,然后我們看下 $request 是個什么鬼
其實就是把array('GET','POST','COOKIE')中的值遍歷出來,其實就可以等價於
<?php
$arr = array('_GET','_POST','_COOKIE');
foreach($arr as $_request)
{
echo $_request."<br>";
}?>
這時候我們知道第一層的 foreach是 輸出 GET,POST,_COOKIE
<?php
$arr = array('_GET','_POST','_COOKIE');
foreach($arr as $_request)
{
foreach($$_request as $_k => $_v){
echo $_k.'=>'.$_v."<br>";
}
}
?>
那我們就看第二層foreach是個什么梗$request 其實就等於 _GET,POST,_COOKIE
而$$request 就等於$GET$POST$COOKIE但是這里的第二層foreach() 其實就是鍵值分離,而我們可以看下
其實簡單來說這3個地方可以被我們控制(代碼功底不行的話可以自己寫下運行一下,像本菜雞一樣,沒上過大學,代碼全自學,如果我寫的demo有什么問題可以說一下,我會糾正,奧利給)
注意看,因為我們這邊是 只使用一個函數,所以我們函數也給復制過來,然后我們在輸出一下,發現就跟我們前面寫的一模一樣
但是我們可以看到的執行順序是 GET > POST > COOKIE
這時候我們可以進行覆蓋,我們再去看一下如何才能進入這一步我們發現上面沒有exit(),die()就忽略了
然后我們看到了那個注釋下面那邊有個 exit() 這時候我們不能讓它進入,不然就無法執行上面講述的代碼了
<?php
foreach($_REQUEST as $_k=>$_v)
{
if( strlen($_k)>0 && m_eregi('^(cfg_|GLOBALS)',$_k) && !isset($_COOKIE[$_k]) )
{
exit('Request var not allow!');
}
}?>
我們不能讓這個if條件成立 && 其實就是跟 and一樣,當着 3個條件滿足,才會exit我們就讓其中一個不滿足
第一個條件:$k就是鍵,我們必須要滿足,我們看下面那個圖,如果我們沒有鍵的話,根本無法控制利用第二個條件:這其實就是一個正則(這是用戶自定義的,就不糾結了),然后正則里面能不能有cfg和 GLOBALS
第三個條件:isset判斷是否存在,如果是,則為 true,但是前面有個 感嘆號這時候意思相反 如果是則為 flase,如果否,則為 true不能有cookie某個值傳參
然后我們只需要滿足其中一個條件,就不會進入exit了
然后變量覆蓋只能覆蓋上面的代碼如果下面的代碼再次定義的話,我們覆蓋也沒什么用的然后注意,這是在 common.php這個文件其實就是通用文件,里面定義的東西全局可能 也會調用等等這時候就去查找一下哪個文件包含了這個common.php其實我們只要找到有危害的地方就行了,像其他地方,有危害即使調用了也沒有卵用
然后這時候我們發現login.php 包含了 common.php這時候我們有看到了下面又調用了一個 check.admin.php了英語會點的就知道,意思就是檢查 是否為admin的意思這時候我們發現找不到這個文件,duomi_INC 在哪里
這時候我們可以去打開login.php
然后進行簡單的修改一下Exit(duomi_INC);這時候就爆出路徑
然后再去尋找一下路徑
成功的在duomiphp下面找到了check.admin.php因為這邊用了session_start 才能對其進行覆蓋登陸后台
然后我們全局搜索找到這個session_start()在這個/interface/comment.php 路徑下這時候我們再去尋找那個check.admin.php進行構造session,進入后台Userlogin 顧名思義,就是用戶登陸,而session需要有duomi_admin_idduomi_group_idduomi_admin_name
<?php
//獲得用戶的權限值
function getgroupid()
{
if($this->groupid!='')
{
return $this->groupid;
}
else
{
return -1;
}
}
function getUserRank()
{
return $this->getgroupid();
}
//獲得用戶的ID
function getUserID()
{
if($this->userID!='')
{
return $this->userID;
}
else
{
return -1;
}
}
//獲得用戶名
function getUserName()
{
if($this->userName!='')
{
return $this->userName;
}
else
{
return -1;
}
}
}
?>
首先是獲取用戶權限這時候我們發現當 groupid=1的時候是系統管理員,我們這就開始構造payload覆蓋session首先得strat一下,我們看上面寫了
interface/comment.php然后我們可以在get傳參里面傳入session
注意看,鍵值這邊的話是
$$
符,所以我們不用再加$
然后需要有3個session,第一個是用戶權限
interface/comment.php?_SESSION[duomi_group_id]=1
看上面源碼,獲取的順序,第二個,userid我們不知道,這也沒固定,那就亂寫個 1
(N[duo其實這里只要權限寫對,其他隨便寫,因為這邊只對權限進行判斷,沒有對用戶id和用戶名進行判斷)
interface/comment.php?_SESSIOmi_group_id]=1&_SESSION['duomi_admin_id']=1
還剩一個用戶名
interface/comment.php?_SESSION[duomi_group_id]=1&_SESSION[duomi_admin_id]=1&_SESSION[duomi_admin_name]=admin
就成功構建好了,把我腦瓜子都整的嗡嗡叫了,我語文是真的不好啊,我丟這里提示我們要登錄,這時候我們傳參進去
這時候去訪問admin頁面發現成功訪問了,因為session是存儲在服務端,然后會持續會話,我們傳參進去報錯,但是session已經存儲到了服務端,這時候訪問admin,就可以進去了。