php bugs代碼審計(jì)基礎(chǔ)詳解
變量覆蓋漏洞
<?php $flag='xxx'; extract($_GET); if(isset($shiyan)) { $content=trim(file_get_contents($flag)); //將讀取$flag內(nèi)容并去除左右空白后保存到$content if($shiyan==$content) { echo'ctf{xxx}'; } else { echo'Oh.no'; } } ?>
重要點(diǎn)為$shiyan==$content
只要滿足這個(gè)條件就可以獲取flag。
首先extract()
函數(shù)的作用為從數(shù)組將變量導(dǎo)入到當(dāng)前符號(hào)表,也就是說我們?nèi)绻麡?gòu)造
xxx.com/index.php?$shiyan=1
則會(huì)生成一個(gè)名字為$shiyan
的變量,值為1。
然后通過isset
函數(shù)來(lái)判斷剛生成的$shiyan
變量是否為null,如果為null就進(jìn)入判斷。
$content
變量則是通過file_get_contents
函數(shù)和trim
函數(shù)來(lái)讀取文件,但是此時(shí)它所讀取的文件$flag
值為xxx,此時(shí)這個(gè)目錄是不存在的,所以它的值為空。
所以我們此時(shí)要做的就是將$shiyan
的值變?yōu)榭占纯伞?/p>
所以構(gòu)造鏈接xxx.com/index.php?$shiyan=&flag=1
即可獲得ctf{xxx}
繞過過濾空白字符
<?php $info = ""; $req = []; $flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; ini_set("display_error", false); //為一個(gè)配置選項(xiàng)設(shè)置值 error_reporting(0); //關(guān)閉所有PHP錯(cuò)誤報(bào)告 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 語(yǔ)法結(jié)構(gòu)提供了遍歷數(shù)組的簡(jiǎn)單方式 foreach($global_var as $key => $value) { $value = trim($value); //trim — 去除字符串首尾處的空白字符(或者其他字符) is_string($value) && $req[$key] = addslashes($value); // is_string — 檢測(cè)變量是否是字符串,addslashes — 使用反斜線引用字符串 } } function is_palindrome_number($number) { $number = strval($number); //strval — 獲取變量的字符串值 $i = 0; $j = strlen($number) - 1; //strlen — 獲取字符串長(zhǎng)度 while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true; } if(is_numeric($_REQUEST['number'])) //is_numeric — 檢測(cè)變量是否為數(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ù)代碼判斷,它需要滿足多個(gè)條件才可以執(zhí)行$info=$flag;
之后echo出來(lái)的才是flag。
if(is_numeric($_REQUEST['number'])) //is_numeric — 檢測(cè)變量是否為數(shù)字或數(shù)字字符串 { $info="sorry, you cann't input a number!"; }
先來(lái)看看第一個(gè)條件,它要求number參數(shù)傳入的內(nèi)容不能為數(shù)字,否則返回sorry, you cann't input a number!
但是它的第二個(gè)要求為數(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!! "; }
導(dǎo)致我們輸入字符串也會(huì)報(bào)錯(cuò)
這里我們用到%00來(lái)繞過is_numeric
函數(shù)的判斷。
根據(jù)報(bào)錯(cuò),再來(lái)看看$value1
,它是$req["number"]
的整數(shù)值,$value2
則為反轉(zhuǎn)之后的$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ù)值,所以我們構(gòu)造如下
以上三個(gè)條件都滿足后,接下來(lái)看看最后一個(gè)條件
if(is_palindrome_number($req["number"])){ $info = "nice! {$value1} is a palindrome number!"; } else { $info=$flag; }
這里調(diào)用了is_palindrome_number()
函數(shù),我們看看函數(shù)內(nèi)容
function is_palindrome_number($number) { $number = strval($number); //strval — 獲取變量的字符串值 $i = 0; $j = strlen($number) - 1; //strlen — 獲取字符串長(zhǎng)度 while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true; }
可以看到這里函數(shù)的作用是判斷數(shù)字是否是對(duì)稱的,我們的要求是讓它執(zhí)行return false
來(lái)執(zhí)行$info=$flag;
所以這里想到的是在數(shù)字前加字符串+
字符串+
在is_numeric
中是被無(wú)視的,也就是說+100與100相等。
所以我們構(gòu)造%00%2b454即可
多重加密
<?php include 'common.php'; $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE); //把一個(gè)或多個(gè)數(shù)組合并為一個(gè)數(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); //從結(jié)果集中取得一行作為關(guān)聯(lián)數(shù)組,或數(shù)字?jǐn)?shù)組,或二者兼有返回根據(jù)從結(jié)果集取得的行生成的數(shù)組,如果沒有更多行則返回 false } } if(isset($requset['token'])) //測(cè)試變量是否已經(jīng)配置。若變量已存在則返回 true 值。其它情形返回 false 值。 { $login = unserialize(gzuncompress(base64_decode($requset['token']))); //gzuncompress:進(jìn)行字符串壓縮 //unserialize: 將已序列化的字符串還原回 PHP 的值 $db = new db(); $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\''); //mysql_real_escape_string() 函數(shù)轉(zhuǎn)義 SQL 語(yǔ)句中使用的字符串中的特殊字符。 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ù)庫(kù)配置文件,所以直接看題,在題目中重點(diǎn)部分為
if(isset($requset['token'])) //測(cè)試變量是否已經(jīng)配置。若變量已存在則返回 true 值。其它情形返回 false 值。 { $login = unserialize(gzuncompress(base64_decode($requset['token']))); //gzuncompress:進(jìn)行字符串壓縮 //unserialize: 將已序列化的字符串還原回 PHP 的值 $db = new db(); $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\''); //mysql_real_escape_string() 函數(shù)轉(zhuǎn)義 SQL 語(yǔ)句中使用的字符串中的特殊字符。 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
得值進(jìn)行反序列化和解壓縮后的值進(jìn)行base64解密。
解密完成后會(huì)帶入上面寫得db
類中得select
方法查詢。
接著就是需要注意得重點(diǎn),if($login['user'] === 'ichunqiu')
即需要傳入的user值為ichunqiu
。
所以我們逆推出來(lái)需要做得就是先將user設(shè)定值為ichunqiu,隨后進(jìn)行base64加密得到值,但是我們剛才說到它在傳值過程中進(jìn)行了反序列話和解壓縮,所以我們也需要進(jìn)行壓縮和序列化,分別用
gzcompress
來(lái)壓縮gzuncompress
解壓縮。
serialize
來(lái)序列化unserialize
反序列化。
最終得到如下代碼,并得到token的值為eJxLtDK0qs60MrBOAuJaAB5uBBQ=
,提交token即可echo $flag
<?php $arr = array(['user'] === 'ichunqiu'); $token = base64_encode(gzcompress(serialize($arr))); print_r($token); ?>
WITH ROLLUP注入
<?php error_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)){ //檢測(cè)變量是否是數(shù)組 $StrValue=implode($StrValue); //返回由數(shù)組元素組合成的字符串 } if (preg_match("/".$ArrReq."/is",$StrValue)==1){ //匹配成功一次后就會(huì)停止匹配 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); //設(shè)置活動(dòng)的 MySQL 數(shù)據(jù)庫(kù) $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'"; $query = mysql_query($sql); //執(zhí)行一條 MySQL 查詢 if (mysql_num_rows($query) == 1) { //返回結(jié)果集中行的數(shù)目 $key = mysql_fetch_array($query); //返回根據(jù)從結(jié)果集取得的行生成的數(shù)組,如果沒有更多行則返回 false if($key['pwd'] == $_POST['pwd']) { print "CTF{XXXXXX}"; }else{ print "亦可賽艇!"; } }else{ print "一顆賽艇!"; } mysql_close($con); ?>
第四題我們先來(lái)看看flag輸出得條件
if($key['pwd'] == $_POST['pwd']) { print "CTF{XXXXXX}"; }else{ print "亦可賽艇!"; }
要滿足post中提交得pwd與$key = mysql_fetch_array($query);
數(shù)據(jù)庫(kù)中讀取到得pwd相等,所以考點(diǎn)在于注入。
但是在AttackFilter
函數(shù)和$filter
中已經(jīng)限制了sql注入得關(guān)鍵字,所以沒辦法直接進(jìn)行注入。
其實(shí)在報(bào)錯(cuò)得過程中已經(jīng)進(jìn)行了提示亦可賽艇!,諧音為因缺思汀也就是WITH ROLLUP
繞過注入
WITH ROLLUP
是對(duì)group by
分組后得結(jié)果進(jìn)行進(jìn)一步得匯總,如果按照列名進(jìn)行分組,因?yàn)榱械脤傩圆煌?,所以?huì)生成一條值null得新數(shù)據(jù),如果查詢結(jié)果時(shí)單一得情況下會(huì)生成一條列為null得數(shù)據(jù)。
我們來(lái)看看演示,值直接進(jìn)行查詢是有結(jié)果得
使用group by
語(yǔ)句分組查詢也是正常顯示
但是當(dāng)我們?cè)?code>group by語(yǔ)句后添加WITH ROLLUP
,可以看到效果如下
但是我們只需要其中第二列得數(shù)據(jù),所以使用limit 1
讀取1條數(shù)據(jù)并使用offset
去除一行數(shù)據(jù)得到我們需要得第二行
pwd得值被設(shè)置為了null,所以此題我們可以通過提交admin' GROUP BY pwd WITH ROLLUP LIMIT 1 OFFSET 1-- -
來(lái)達(dá)到$key['pwd'] == $_POST['pwd']
得條件并獲取flag。
erge截?cái)?/h2>
<?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>';
}
}
?>
<?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>'; } } ?>
先來(lái)梳理流程 首先條件一用ereg
函數(shù)來(lái)寫死get參數(shù)password得值必須是數(shù)字大小寫字母,否則輸出You password must be alphanumeric
。
第二個(gè)條件為strlen($_GET['password']) < 8 && $_GET['password'] > 9999999
也就是必須長(zhǎng)度小于8但是值又要大于9999999。
所以我們需要用科學(xué)計(jì)數(shù)法來(lái)繞過這里得限制1e7
為10得7次方10000000。
第三個(gè)條件為strpos ($_GET['password'], '*-*') !== FALSE
在值中必須存在*-*
如果滿足此條件,就沒辦法滿足條件一,所以這里可以采用%00
截?cái)喾▉?lái)進(jìn)行繞過,因?yàn)?code>ereg函數(shù)遇到%00后就不會(huì)繼續(xù)進(jìn)行判斷
?password=1e7%00*-*
即可滿足全部條件,執(zhí)行die('Flag: ' . $flag);
來(lái)獲取flag
strcmp比較字符串
<?php $flag = "flag"; if (isset($_GET['a'])) { if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果兩者相等,返回 0。 //比較兩個(gè)字符串(區(qū)分大小寫) die('Flag: '.$flag); else print 'No'; } ?>
這題得考點(diǎn)在于strcmp
函數(shù),它的作用在于兩個(gè)字符串相比較,如果兩者相等就會(huì)==0
。
此函數(shù)是用來(lái)處理字符串參數(shù)的,如果提交的值是數(shù)組的話會(huì)返回個(gè)null
在判斷中使用的是==
等值符,如果類型不相同的情況下會(huì)轉(zhuǎn)換為同類型進(jìn)行比較,所以null
==0
,執(zhí)行die('Flag: '.$flag);
所以我們提交一個(gè)數(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); else echo '<p>Invalid password.</p>'; } else echo '<p>Login first!</p>'; ?>
這題的考點(diǎn)在于sha1($_GET['name']) === sha1($_GET['password'])
他這里使用的是===
等同符,他要求兩邊值得類型相同,才會(huì)去比較值,否則會(huì)直接返回false
首先來(lái)看條件一$_GET['name'] == $_GET['password']
name要與password不相等才會(huì)執(zhí)行else if
但是else if
又要求===
,所以我們可以利用sha1
函數(shù)不能處理數(shù)組得機(jī)制來(lái)繞過
即?name[]=1&password[]=2
既滿足了name與password不相等,也滿足了因sha1
無(wú)法處理數(shù)組,導(dǎo)致返回值為false=false
所以會(huì)執(zhí)行die('Flag: '.$flag);
到此這篇關(guān)于php bugs代碼審計(jì)基礎(chǔ)詳解的文章就介紹到這了,更多相關(guān)php bugs內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PHP+ajax實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)菜單功能示例
這篇文章主要介紹了PHP+ajax實(shí)現(xiàn)二級(jí)聯(lián)動(dòng)菜單功能,涉及php結(jié)合ajax的數(shù)據(jù)交互與頁(yè)面元素動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-08-08PHP計(jì)算數(shù)組中值的和與乘積的方法(array_sum與array_product函數(shù))
這篇文章主要介紹了PHP計(jì)算數(shù)組中值的和與乘積的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了array_sum與array_product函數(shù)的功能與使用方法,需要的朋友可以參考下2016-04-04PHP計(jì)算當(dāng)前坐標(biāo)3公里內(nèi)4個(gè)角落的最大最小經(jīng)緯度實(shí)例
這篇文章主要介紹了PHP計(jì)算當(dāng)前坐標(biāo)3公里內(nèi)4個(gè)角落的最大最小經(jīng)緯度的方法,涉及PHP數(shù)學(xué)運(yùn)算的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-02-02php使用get_class_methods()函數(shù)獲取分類的方法
這篇文章主要介紹了php使用get_class_methods()函數(shù)獲取分類的方法,結(jié)合實(shí)例形式分析了get_class_methods()函數(shù)獲取類中成員方法的使用技巧,需要的朋友可以參考下2016-07-07