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-08
PHP計(jì)算數(shù)組中值的和與乘積的方法(array_sum與array_product函數(shù))
這篇文章主要介紹了PHP計(jì)算數(shù)組中值的和與乘積的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了array_sum與array_product函數(shù)的功能與使用方法,需要的朋友可以參考下2016-04-04
PHP計(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-02
php使用get_class_methods()函數(shù)獲取分類的方法
這篇文章主要介紹了php使用get_class_methods()函數(shù)獲取分類的方法,結(jié)合實(shí)例形式分析了get_class_methods()函數(shù)獲取類中成員方法的使用技巧,需要的朋友可以參考下2016-07-07

