變量覆蓋-高級篇(動態覆蓋,extract綜合)


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 是如何覆蓋的。


免責聲明!

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



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