1.變量覆蓋:
①:針對extract函數的變量覆蓋漏洞:
1 <?php 2 @error_reporting(E_ALL^E_NOTICE); 3 require('config.php'); 4 5 if($_GET['show_source'] === '1') { 6 highlight_file(__FILE__); 7 exit; 8 } 9 10 $user = null; 11 12 // connect to database 13 14 if(!empty($_POST['data'])) { 15 try { 16 $data = json_decode($_POST['data'], true); 17 } catch (Exception $e) { 18 $data = []; 19 } 20 extract($data); 21 if($users[$username] && strcmp($users[$username], $password) == 0) { 22 $user = $username; 23 } 24 }
1 <?php if($user == 'admin') printf("<code>%s</code>", htmlentities($flag)); ?>
當$user=“admin”時,輸出flag
向上看,$user =$username
則需要使$username="admin"
if成立的條件是$users[$username]不為空並且需要滿足$users[$username]=$password
我們在不知道$password的情況下需要使$users[$username]=$password成立。
再向上看extract()函數,此函數為變量注冊函數,將數組中的數據以鍵名為變量名,鍵值為變量值的形式注冊變量。
那在這就存在變量覆蓋問題,我們可以傳入任意構造$password的值覆蓋原有的$password的值。
即構造data={"username":"admin","password":"123","users":{"admin":"123"}}即可得到flag
②:基於parse_str()函數的變量覆蓋漏洞
1 <meta charset="utf-8"> 2 error_reporting(0); 3 if (empty($_GET['b'])) { 4 show_source(__FILE__); 5 die(); 6 }else{ 7 include('flag.php'); 8 $a = "www.XMAN.com"; 9 $b = $_GET['b']; 10 @parse_str($b); 11 if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) { 12 echo $flag; 13 }else{ 14 exit('你的答案不對0.0'); 15 } 16 }
輸出flag的條件為$a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')
parse_str — 將字符串解析成多個變量,如果參數str是URL傳遞入的查詢字符串(query string),則將它解析為變量並設置到當前作用域。
由於此函數的作用可以將$a[0]的值覆蓋,則在需要找一對md5碰撞即可。
即傳b=a[0]=s155964671a即可得到flag。
③:基於register_globals的變量覆蓋漏洞:
1 <?php 2 echo "Register_globals: ".(int)ini_get("register_globals")."<br/>"; 3 4 if ($auth){ 5 echo "private!"; 6 } 7 ?>
當register_globals=OFF時,這段代碼不會出問題。
但是當register_globals=ON時,提交請求URL:http://www.a.com/test.php?auth=1,變量$auth將自動得到賦值。得到的結果為
Register_globals:1
小記:如果上面的代碼中,已經對變量$auth賦了初始值,比如$auth=0,那么即使在URL中有/test.PHP?auth=1,也不會將變量覆蓋,也就是說不會打印出private!
通過$GLOBALS獲取的變量,也可能導致變量覆蓋。
1 <?php 2 echo "Register_globals:".(int)ini_get("register_globals")."<br/>"; 3 if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) unset(${$k}); 4 print $a; 5 print $_GET[b]; 6 ?>
變量$a未初始化,在register_globals=ON時,再嘗試控制“$a”的值(http://www.a.com/test1.php?a=1&b=2),會因為這段代碼而出錯(因為$a沒有值)。
而當嘗試注入“GLOBALS[a]”以覆蓋全局變量時(http://www.a.com/test1.php?GLOBALS[a]=1&b=2),則可以成功控制變量“$a”的值。這是因為unset()默認只會銷毀局部變量,要銷毀全局變量必須使用$GLOBALS。
而在register_globals=OFF時,則無法覆蓋到全局變量。
小記:register_globals的意思是注冊為全局變量,所以當On的時候,傳遞過來的值會被直接注冊為全局變量而直接使用,當為OFF的時候,就需要到特定的數組中去得到它。unset用於釋放給定的變量
④:遍歷初始化變量
1 <? 2 $chs = ''; 3 if($_POST && $charset != 'utf-8'){ 4 $chs = new Chinese('UTF-8', $charset); 5 foreach($_POST as $key => $value){ 6 $$key = $chs->Convert($value); 7 } 8 unset($chs); 9 } 10 ?>
由於php中可以使用$$聲明變量,因此當在遍歷數組時可能會覆蓋原來的值。
若提交參數chs,則可覆蓋變量"$chs"的值。
小記:在代碼審計時需要注意類似“$$k”的變量賦值方式有可能覆蓋已有的變量,從而導致一些不可控制的結果
⑤:import_request_variables變量覆蓋
1 <?php 2 $auth = '0'; 3 import_request_variables('G'); 4 5 if($auth == 1){ 6 echo "private!"; 7 }else{ 8 echo "public!"; 9 } 10 ?>
當用戶輸入http://www.a.com/test1.php?auth=1時,網頁上會輸出private!
import_request_variables('G')指定導入GET請求中的變量,從而導致變量覆蓋。
小記:import_request_variables — 將 GET/POST/Cookie 變量導入到全局作用域中。如果你禁止了 register_globals,但又想用到一些全局變量,那么此函數就很有用。