php bugs代碼審計基礎詳解
目錄
- 變量覆蓋漏洞
- 繞過過濾空白字符
- 多重加密
- WITH ROLLUP注入
- erge截斷
- strcmp比較字符串
- sha()函數(shù)比較繞過
變量覆蓋漏洞
<?php$flag="xxx"; extract($_GET); if(isset($shiyan)) { $content=trim(file_get_contents($flag)); //將讀取$flag內容并去除左右空白后保存到$content if($shiyan==$content) { echo"ctf{xxx}"; } else { echo"Oh.no"; } }?>
重要點為$shiyan==$content
只要滿足這個條件就可以獲取flag。
首先extract()
函數(shù)的作用為從數(shù)組將變量導入到當前符號表,也就是說我們如果構造
xxx.com/index.php?$shiyan=1
則會生成一個名字為$shiyan
的變量,值為1。
然后通過isset
函數(shù)來判斷剛生成的$shiyan
變量是否為null,如果為null就進入判斷。
$content
變量則是通過file_get_contents
函數(shù)和trim
函數(shù)來讀取文件,但是此時它所讀取的文件$flag
值為xxx,此時這個目錄是不存在的,所以它的值為空。
所以我們此時要做的就是將$shiyan
的值變?yōu)榭占纯伞?/p>
所以構造鏈接xxx.com/index.php?$shiyan=&flag=1
即可獲得ctf{xxx}
繞過過濾空白字符
<?php$info = ""; $req = [];$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";ini_set("display_error", false); //為一個配置選項設置值error_reporting(0); //關閉所有PHP錯誤報告if(!isset($_GET["number"])){ header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP頭顯示hint 26966dc52e85af40f59b4fe73d8c323a.txt die("have a fun!!"); //die — 等同于 exit()}foreach([$_GET, $_POST] as $global_var) { //foreach 語法結構提供了遍歷數(shù)組的簡單方式 foreach($global_var as $key => $value) { $value = trim($value); //trim — 去除字符串首尾處的空白字符(或者其他字符)is_string($value) && $req[$key] = addslashes($value); // is_string — 檢測變量是否是字符串,addslashes — 使用反斜線引用字符串 } } function is_palindrome_number($number) { $number = strval($number); //strval — 獲取變量的字符串值 $i = 0; $j = strlen($number) - 1; //strlen — 獲取字符串長度 while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true; } if(is_numeric($_REQUEST["number"])) //is_numeric — 檢測變量是否為數(shù)字或數(shù)字字符串 { $info="sorry, you cann"t input a number!";}elseif($req["number"]!=strval(intval($req["number"]))) //intval — 獲取變量的整數(shù)值{ $info = "number must be equal to it"s integer!! "; }else{ $value1 = intval($req["number"]); $value2 = intval(strrev($req["number"])); if($value1!=$value2){ $info="no, this is not a palindrome number!"; } else { if(is_palindrome_number($req["number"])){ $info = "nice! {$value1} is a palindrome number!"; } else { $info=$flag; } }}echo $info;
根據(jù)代碼判斷,它需要滿足多個條件才可以執(zhí)行$info=$flag;
之后echo出來的才是flag。
if(is_numeric($_REQUEST["number"])) //is_numeric — 檢測變量是否為數(shù)字或數(shù)字字符串 { $info="sorry, you cann"t input a number!";}
先來看看第一個條件,它要求number參數(shù)傳入的內容不能為數(shù)字,否則返回sorry, you cann't input a number!
但是它的第二個要求為數(shù)字必須為整數(shù),否則輸出number must be equal to it's integer!!
elseif($req["number"]!=strval(intval($req["number"]))) //intval — 獲取變量的整數(shù)值{ $info = "number must be equal to it"s integer!! "; }
導致我們輸入字符串也會報錯
這里我們用到%00來繞過is_numeric
函數(shù)的判斷。
根據(jù)報錯,再來看看$value1
,它是$req["number"]
的整數(shù)值,$value2
則為反轉之后的$req["number"]
的整數(shù)值。
$value1 = intval($req["number"]);$value2 = intval(strrev($req["number"])); if($value1!=$value2){ $info="no, this is not a palindrome number!"; }
所以第三步要滿足的條件為,它必須為回文數(shù)即從左往右和從右往左讀取都要相同的數(shù)值,所以我們構造如下
以上三個條件都滿足后,接下來看看最后一個條件
if(is_palindrome_number($req["number"])){ $info = "nice! {$value1} is a palindrome number!"; } else { $info=$flag; }
這里調用了is_palindrome_number()
函數(shù),我們看看函數(shù)內容
function is_palindrome_number($number) { $number = strval($number); //strval — 獲取變量的字符串值 $i = 0; $j = strlen($number) - 1; //strlen — 獲取字符串長度 while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true; }
可以看到這里函數(shù)的作用是判斷數(shù)字是否是對稱的,我們的要求是讓它執(zhí)行return false
來執(zhí)行$info=$flag;
所以這里想到的是在數(shù)字前加字符串+
字符串+
在is_numeric
中是被無視的,也就是說+100與100相等。
所以我們構造%00%2b454即可
多重加密
<?php include "common.php"; $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE); //把一個或多個數(shù)組合并為一個數(shù)組 class db {public $where;function __wakeup(){ if(!empty($this->where)) {$this->select($this->where); }}function select($where){ $sql = mysql_query("select * from user where ".$where); //函數(shù)執(zhí)行一條 MySQL 查詢。 return @mysql_fetch_array($sql); //從結果集中取得一行作為關聯(lián)數(shù)組,或數(shù)字數(shù)組,或二者兼有返回根據(jù)從結果集取得的行生成的數(shù)組,如果沒有更多行則返回 false} } if(isset($requset["token"])) //測試變量是否已經配置。若變量已存在則返回 true 值。其它情形返回 false 值。 {$login = unserialize(gzuncompress(base64_decode($requset["token"])));//gzuncompress:進行字符串壓縮//unserialize: 將已序列化的字符串還原回 PHP 的值$db = new db();$row = $db->select("user=\"".mysql_real_escape_string($login["user"])."\"");//mysql_real_escape_string() 函數(shù)轉義 SQL 語句中使用的字符串中的特殊字符。if($login["user"] === "ichunqiu"){ echo $flag;}else if($row["pass"] !== $login["pass"]){ echo "unserialize injection!!";}else{ echo "(╯‵□′)╯︵┴─┴ ";} }else{header("Location: index.php?error=1"); }?>
因題目中并沒有給出數(shù)據(jù)庫配置文件,所以直接看題,在題目中重點部分為
if(isset($requset["token"])) //測試變量是否已經配置。若變量已存在則返回 true 值。其它情形返回 false 值。 {$login = unserialize(gzuncompress(base64_decode($requset["token"])));//gzuncompress:進行字符串壓縮//unserialize: 將已序列化的字符串還原回 PHP 的值$db = new db();$row = $db->select("user=\"".mysql_real_escape_string($login["user"])."\"");//mysql_real_escape_string() 函數(shù)轉義 SQL 語句中使用的字符串中的特殊字符。if($login["user"] === "ichunqiu"){ echo $flag;}else if($row["pass"] !== $login["pass"]){ echo "unserialize injection!!";}else{ echo "(╯‵□′)╯︵┴─┴ ";} }else{header("Location: index.php?error=1"); }
條件1為判斷是否存在token
參數(shù),如果存在,那么就將token
得值進行反序列化和解壓縮后的值進行base64解密。
解密完成后會帶入上面寫得db
類中得select
方法查詢。
接著就是需要注意得重點,if($login['user'] === 'ichunqiu')
即需要傳入的user值為ichunqiu
。
所以我們逆推出來需要做得就是先將user設定值為ichunqiu,隨后進行base64加密得到值,但是我們剛才說到它在傳值過程中進行了反序列話和解壓縮,所以我們也需要進行壓縮和序列化,分別用
gzcompress
來壓縮gzuncompress
解壓縮。
serialize
來序列化unserialize
反序列化。
最終得到如下代碼,并得到token的值為eJxLtDK0qs60MrBOAuJaAB5uBBQ=
,提交token即可echo $flag
<?php$arr = array(["user"] === "ichunqiu");$token = base64_encode(gzcompress(serialize($arr)));print_r($token);?>
WITH ROLLUP注入
<?phperror_reporting(0);if (!isset($_POST["uname"]) || !isset($_POST["pwd"])) { echo "<form action="" method="post">"."<br/>"; echo "<input name="uname" type="text"/>"."<br/>"; echo "<input name="pwd" type="text"/>"."<br/>"; echo "<input type="submit" />"."<br/>"; echo "</form>"."<br/>"; echo "<!--source: source.txt-->"."<br/>"; die;}function AttackFilter($StrKey,$StrValue,$ArrReq){ if (is_array($StrValue)){//檢測變量是否是數(shù)組$StrValue=implode($StrValue);//返回由數(shù)組元素組合成的字符串 } if (preg_match("/".$ArrReq."/is",$StrValue)==1){ //匹配成功一次后就會停止匹配print "水可載舟,亦可賽艇!";exit(); }}$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";foreach($_POST as $key=>$value){ //遍歷數(shù)組 AttackFilter($key,$value,$filter);}$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");if (!$con){ die("Could not connect: " . mysql_error());}$db="XXXXXX";mysql_select_db($db, $con);//設置活動的 MySQL 數(shù)據(jù)庫$sql="SELECT * FROM interest WHERE uname = "{$_POST["uname"]}"";$query = mysql_query($sql); //執(zhí)行一條 MySQL 查詢if (mysql_num_rows($query) == 1) { //返回結果集中行的數(shù)目 $key = mysql_fetch_array($query);//返回根據(jù)從結果集取得的行生成的數(shù)組,如果沒有更多行則返回 false if($key["pwd"] == $_POST["pwd"]) {print "CTF{XXXXXX}"; }else{print "亦可賽艇!"; }}else{ print "一顆賽艇!";}mysql_close($con);?>
第四題我們先來看看flag輸出得條件
if($key["pwd"] == $_POST["pwd"]) {print "CTF{XXXXXX}"; }else{print "亦可賽艇!"; }
要滿足post中提交得pwd與$key = mysql_fetch_array($query);
數(shù)據(jù)庫中讀取到得pwd相等,所以考點在于注入。
但是在AttackFilter
函數(shù)和$filter
中已經限制了sql注入得關鍵字,所以沒辦法直接進行注入。
其實在報錯得過程中已經進行了提示亦可賽艇!,諧音為因缺思汀也就是WITH ROLLUP
繞過注入
WITH ROLLUP
是對group by
分組后得結果進行進一步得匯總,如果按照列名進行分組,因為列得屬性不同,所以會生成一條值null得新數(shù)據(jù),如果查詢結果時單一得情況下會生成一條列為null得數(shù)據(jù)。
我們來看看演示,值直接進行查詢是有結果得
使用group by
語句分組查詢也是正常顯示
但是當我們在group by
語句后添加WITH ROLLUP
,可以看到效果如下
但是我們只需要其中第二列得數(shù)據(jù),所以使用limit 1
讀取1條數(shù)據(jù)并使用offset
去除一行數(shù)據(jù)得到我們需要得第二行
pwd得值被設置為了null,所以此題我們可以通過提交admin' GROUP BY pwd WITH ROLLUP LIMIT 1 OFFSET 1-- -
來達到$key['pwd'] == $_POST['pwd']
得條件并獲取flag。
erge截斷
<?php $flag = "flag";if (isset ($_GET["password"])) { if (ereg ("^[a-zA-Z0-9]+$", $_GET["password"]) === FALSE) { echo "<p>You password must be alphanumeric</p>"; } else if (strlen($_GET["password"]) < 8 && $_GET["password"] > 9999999) { if (strpos ($_GET["password"], "*-*") !== FALSE) //strpos — 查找字符串首次出現(xiàn)的位置 { die("Flag: " . $flag); } else {echo("<p>*-* have not been found</p>"); } } else {echo "<p>Invalid password</p>"; } } ?>
先來梳理流程 首先條件一用ereg
函數(shù)來寫死get參數(shù)password得值必須是數(shù)字大小寫字母,否則輸出You password must be alphanumeric
。
第二個條件為strlen($_GET['password']) < 8 && $_GET['password'] > 9999999
也就是必須長度小于8但是值又要大于9999999。
所以我們需要用科學計數(shù)法來繞過這里得限制1e7
為10得7次方10000000。
第三個條件為strpos ($_GET['password'], '*-*') !== FALSE
在值中必須存在*-*
如果滿足此條件,就沒辦法滿足條件一,所以這里可以采用%00
截斷法來進行繞過,因為ereg
函數(shù)遇到%00后就不會繼續(xù)進行判斷
?password=1e7%00*-*
即可滿足全部條件,執(zhí)行die('Flag: ' . $flag);
來獲取flag
strcmp比較字符串
<?php$flag = "flag";if (isset($_GET["a"])) { if (strcmp($_GET["a"], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果兩者相等,返回 0。 //比較兩個字符串(區(qū)分大小寫) die("Flag: ".$flag); else print "No"; }?>
這題得考點在于strcmp
函數(shù),它的作用在于兩個字符串相比較,如果兩者相等就會==0
。
此函數(shù)是用來處理字符串參數(shù)的,如果提交的值是數(shù)組的話會返回個null
在判斷中使用的是==
等值符,如果類型不相同的情況下會轉換為同類型進行比較,所以null
==0
,執(zhí)行die('Flag: '.$flag);
所以我們提交一個數(shù)組類型的值即可?a[]=1
。
sha()函數(shù)比較繞過
<?php$flag = "flag";if (isset($_GET["name"]) and isset($_GET["password"])) { if ($_GET["name"] == $_GET["password"])echo "<p>Your password can not be your name!</p>"; else if (sha1($_GET["name"]) === sha1($_GET["password"])) die("Flag: ".$flag); elseecho "<p>Invalid password.</p>";}else echo "<p>Login first!</p>";?>
這題的考點在于sha1($_GET['name']) === sha1($_GET['password'])
他這里使用的是===
等同符,他要求兩邊值得類型相同,才會去比較值,否則會直接返回false
首先來看條件一$_GET['name'] == $_GET['password']
name要與password不相等才會執(zhí)行else if
但是else if
又要求===
,所以我們可以利用sha1
函數(shù)不能處理數(shù)組得機制來繞過
即?name[]=1&password[]=2
既滿足了name與password不相等,也滿足了因sha1
無法處理數(shù)組,導致返回值為false=false
所以會執(zhí)行die('Flag: '.$flag);
到此這篇關于php bugs代碼審計基礎詳解的文章就介紹到這了,更多相關php bugs內容請搜索以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持!
