0x00 原理
??變數覆寫漏洞可以讓用戶定義的變數值覆寫原有程式變數,可控制原程式邏輯,
0x01 代碼
<?php
highlight_file('index.php');
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}
}
}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['daiker']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_Ginclude($_GET['file']){
echo 'flag={你猜猜}';
}
}
?>
0x02 代碼審計
??首先對一下代碼進行分析
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
??我們知道foreach是php遍歷陣列的一種方式,這里waf函式遍歷$a里面的變數名和變數值,如果變數名中含有flag,則報錯
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}
}
}
??將_POST,_GET,_COOKIE中的值作為 $__R 值判斷,是否存在以 $__R 值為變數名的變數,如果有 繼續遍歷這個變數里的變數名和變數值,如果有里面的變數名存在而且變數名等于變數值就銷毀變數__k
,,這樣說確實有點亂,但是后面這段foreach 和 熟悉的$$ 應該能想到是通過GET 進行傳參,也就是說 $__k==$_GET[flag] ,因為后面要判斷flag傳參,所以這里只能是flag, $$__k 是取 $_GET[flag]中的值作為變數名,
之后結合實戰可能更好理解點吧
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['daiker']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_GET['daiker'])){
echo 'flag={你猜猜}';
}
}
??通過 if 判斷 是否存在POST,GET,COOKIE 變數傳參,如果存在 用waf去檢測
然后再使用extract($_POST, EXTR_SKIP),extract($_GET, EXTR_SKIP)將變數寫入符號表保存起來,而且使用到EXTR_SKIP關鍵字后不會覆寫之前的變數,
??最后判斷是否通過flag進行了傳參,傳了后,將$flag中的值和$daiker進行比較,相同則結束, 不相同 則 通過md5加密 進行比較,這里存在個php若型別,
我們可以通過兩種md5加密后為0e開頭的字串進行繞過,比如QNKCDZO和s878926199a
0x03 實戰
??雖然waf函式是最先定義的,但是它是在消除變數后使用的,也就是說題目意思其實是讓我們先進行變數清除-》unset函式,不然無法繞過waf函式,因為waf函式匹配了'_POST', '_GET', '_COOKIE',而且后面有要求是通過flag進行傳參,所以只能先通過flag和daiker傳入對應的值,
簡單測驗一下吧
<?php
highlight_file('index.php');
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
echo '成功消除函式';
}
}
}
var_dump($_POST);
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['daiker']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_Ginclude($_GET['file']){
echo 'flag={你猜猜}';
}
}
?>
??其實如果理清了思路,多測測就能得到flag了,但是想要深入了解 foreach 是不行的,這里我們構造GET:?_POST[flag]&_POST[daiker] POST: flag=&daike= 來做個小實驗


??可以說明當我們傳入?_POST[flag]&_POST[daiker]時 ,$_GET 保存了 以 _POST為鍵 值為 flag 和daiker 的陣列 的陣列,然后通過foreach遍歷因為有'_POST', '_GET', '_COOKIE' 所以總共遍歷了3次, 遍歷到_GET取出陣列賦值到$__R保存,$$__R其實就是$_POST 判定是否存在POST,這里我們是POST提交了引數的,所以存在,繼續執行,取出里面的鍵和值 其實就是 flag:NULL 和 daiker:NULL 判斷是否存在 $flag 由于我們POST提交了 flag和daiker,所以它遍歷時可判斷存在$flag,而且post提交的flag的值正好和GET里flag值一樣都為空,通過unset 消去了匹配到的$flag和$daiker,使得$_POST里面沒了內容,
<?php
highlight_file('index.php');
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
echo '成功消除函式';
}
}
}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
var_dump($_POST);
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['daiker']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_Ginclude($_GET['file']){
echo 'flag={你猜猜}';
}
}
?>

??根據上面的運行結果我們可以看到之前被消去的$_POST突然回來了,賦值后應該會明顯點,

??也就是說知道了這個原理,我們可以通過GET: ?flag=aa&daiker=bb POST: _GET[flag]=aa&_POST[daiker]=bb , 之前故意調換順序的就是為了讓這里變得更加清楚點,
??這里正好和前面反過來,GET傳進去了后 遍歷時就存在 $flag 變數 而$_GET[flag] 通過 foreach 進行鍵值得配對,最后洗掉了$_GET中的$flag和$daiker,然后通過extract函式,
對extract進行測驗
<?php
if($_POST){
extract($_POST, EXTR_SKIP);
echo '成功';
}
var_dump($_GET);
?>

??測驗失敗,說明 extract 是不能直接進行變數的覆寫的,那只能說明 unset 沒有洗掉干凈了,可能涉及到指標吧,如果unset是使用參考置NULL,而extract是通過指標訪問陣列記憶體的話就有可能,因為記憶體地址保存的值還是原來的GET,通過extract可以獲取到之前GET中的值,從而繞過了unset,
??因為_GET雖然沒了但是POST_存在,會通過指標去訪問原來保存了_GET變數的記憶體地址,直接取出原來的值,(感覺設定不能覆寫之前變數的關鍵字也和記憶體地址有干系),
知道了這個原理,后面的弱型別也就簡單了,只要把aa和bb改成QNKCDZO和s878926199a,嘗試一下,

成功爆出flag,但是我想看看 extract 是如何覆寫的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/285864.html
標籤:訊息安全
