變數覆蓋漏洞的分析與總結
- 2019 年 10 月 8 日
- 筆記
前言
最近在跟師傅們討論程式碼審計技巧的時候,好幾個師傅都提到了變數覆蓋漏洞,對於這一塊的知識我並不是了解很多,網上的說明或多或少的都有一些粗略和不足,所以在這幾天閑暇之餘,我特意地將PHP變數覆蓋漏洞進行了系統的總結,在此記錄一下,個人難免會有疏漏和不足之處,非常歡迎各位師傅的補充與糾正
簡介
我認為一個比較正確的定義是:在PHP程式碼中將自定義參數值替換為原有參數值的情況稱為變數覆蓋。
變數覆蓋漏洞一般單體作用很小,並不能造成很大危害,但是在與其他應用程式碼或漏洞結合後,其造成的危害可能是無法估量的,最簡單的例如購買商品的支付系統,某些爆出的0元支付下單的BUG就常常可以見到變數覆蓋漏洞的身影。
常見的漏洞引發類型
由$$變數賦值引發的覆蓋
$$是一種可變變數的寫法,它可以使一個普通變數的值作為可變變數的名字,這種類型常常會使用遍歷的方式來釋放變數的程式碼,最常見的就是foreach的遍歷,示例程式碼如下:
<?php $yml = 10; echo $yml; echo "<br>"; foreach ($_POST as $k => $v){ $$k = $v; echo $yml; } ?>無任何操作時的正常輸出:

當post內容為yml=1000時:

很明顯看到這裡$yml的值變為了1000,我們成功的完成了一次變數覆蓋。
再拿出一個我前幾天給學弟們出的一個小題為例子:
程式碼:
<?php include('flag.php'); $flag = 'flag{it_Is_Y0ur_flag}'; foreach ($_POST as $key => $value) { $a = $value; $$$key=$value; $ccut = $flag; $yml = $_GET["flag"]; if ($yml == "iwantflag") { if ($ccut == "flag") { echo $fl4g; } else { echo "you will get it"; } } else { echo "nonono"; } # code... } highlight_file(__FILE__); ?>題目的本質還是變數覆蓋,題目中核心的部分就是需要將$flag的值由flag{itIsY0ur_flag}覆蓋為flag,仔細閱讀程式碼流程再結合上面的例子就可以輕鬆解出,我這裡直接給出payload:

extract()函數使用不當導致的變數覆蓋
該函數可以將變數從數組中導入當前的符號表
我們看一下在w3school中函數的定義

這裡我們要注意一下該函數的第二個參數,該參數的選擇就確定了將變數導入符號表時的行為,在實際生產生活中,我們常常使用的值有EXTROVERWRITE和EXTRSKIP。
當值設定為EXTRSKIP時,在導入符號表的過程中,如果變數名發生衝突,則跳過該變數不進行覆蓋,當值為EXTROVERWRITE時如果發生衝突,則覆蓋已有變數,該函數在不指定第二個參數時默認使用EXTR_OVERWRITE,這就為我們提供了覆蓋的可能。
示例程式碼:
<?php $yml = 10; echo 'out0:'.$yml; extract($_POST); echo '<br>'; echo "out1:".$yml; ?>
無post輸入時

輸入yml=199時:

我們成功的將$yml的值從10覆蓋為了199
全局變數的覆蓋
如果某些變數沒有被初始化,並且黑客可以控制,將會是一件很危險的事情,在這種情況下,漏洞觸發的前提是registerglobals為ON(registerglobals的值可以在php.ini中修改,我在個人的PHPstudy上發現在php5.2版本後該值默認是OFF)
示例程式碼:
<?php echo (int)ini_get("register_globals"); echo '<br>'; echo "yml=".$yml; ?>當register_globals為OFF時

可以我們無法將未初始化的變數進行註冊,但是當register_globals的值為ON時,結果如下

可以看到我們成功註冊了一個未初始化的變數
還有一種通過$GLOBALS獲取的變數在使用不當時也會導致變數覆蓋,同樣漏洞觸發的前提是register_globals為ON
還是用上面的示例程式碼:

我們成功通過注入GLOBALS[yml]來改變$yml的值
parse_str()函數使用不當導致的覆蓋
該函數可以把查詢的字元串解析到變數中,我們來看一下w3school中對該函數的定義

這裡指的注意的是,如果未設置第二個參數的值,由該函數設置的變數將覆蓋已存在的同名變數
所以當我們沒有設置函數的第二個參數時,惡意攻擊者很可能通過特定的輸入來改變程式碼中已定義的變數的值
示例:
<?php $yml = "cool"; echo "out0:".$yml; echo "<br>"; $a = $_GET['a']; parse_str($a); echo "out1:".$yml; ?>
在這裡我們沒有設置parse_str()函數的第二個參數,現在我們來嘗試構造同名變數

可以看到我們成功的使用構造同名變數的方法覆蓋掉了$yml的原有值
importrequestvariables所導致的變數覆蓋
該函數可以將 GET/POST/Cookie 變數導入到全局作用域中,我們看一下該函數的定義(在PHP5.4之後的版本中,該函數將不再使用)

該函數的第二個參數用於設置註冊變數的前綴,漏洞觸發的原因是當第二個參數未進行設置時,將會出現覆蓋全局變數的情況
示例:
<?php $yml = "happy"; echo "out0:".$yml; echo "<br>"; import_request_variables('P'); echo "out1:".$yml; ?>無輸入時:

程式碼沒有設置importrequestvariables的第二個參數,我們來設置同名變數輸入看是否能夠進行覆蓋

在這裡我們成功的註冊了同名的全局變數將原有變數的值進行了覆蓋。
漏洞防禦
對於第一種情況,在審計的時候要注意$$的賦值語句,使用恰當的方式防止變數覆蓋漏洞的發生
對於第二種情況,在使用extract()函數時,可以指定將第二個參數設置為EXTRSKIP,並且留意變數的獲取順序,控制好用戶的輸入。
對於第三種情況,強烈推薦將registerglobals設置為Off
對於第四種情況,我們應該在使用parse_str()時養成指定第二個參數的習慣,這樣才能避免變數被覆蓋
對於最後一種情況,我們同樣要指定第二個函數參數來設置要註冊的變數前綴,不過這個函數現在已經很少用了。
總結
變數覆蓋漏洞觸發的靈活性較高,但我們只要抓住根本問題,控制好用戶輸入並且規範程式碼的書寫,還是可以進行防範的,該漏洞經常在ctf題目中作為一個考點出現,只要我們緊跟程式碼邏輯,還是很容易解出題目的。
最後還是要祝安恆學院越辦越好。