雖然PHP是世界上最好的語言,但是也有一些因為弱類型語言的安全性問題出現。WordPress歷史上就出現過由於PHP本身的缺陷而造成的一些安全性問題,如CVE-2014-0166 中的cookie偽造就是利用了PHP Hash比較的缺陷。 當然一般這種情況實戰中用到的不是很多,但是在CTF競賽中卻是一個值得去考察的一個知識點,特此記錄總結之。
一、精度繞過缺陷理論在用PHP進行浮點數的運算中,經常會出現一些和預期結果不一樣的值,這是由於浮點數的精度有限。儘管取決於系統,PHP 通常使用 IEEE 754 雙精度格式,則由於取整而導致的最大相對誤差為 1.11e-16。非基本數學運算可能會給出更大誤差,並且要考慮到進行複合運算時的誤差傳遞。下面看一個有趣的例子:
以十進位能夠精確表示的有理數如 0.1 或 0.7,無論有多少尾數都不能被內部所使用的二進位精確表示,因此不能在不丟失一點點精度的情況下轉換為二進位的格式。這就會造成混亂的結果:例如,floor((0.1+0.7)*10) 通常會返回 7 而不是預期中的 8,因為該結果內部的表示其實是類似 7.9999999999999991118…。
實踐問鼎杯2017 老眼昏花網上很多write-up感覺就像是看著答案寫write-up,個人感覺真正的write-up中應該體現自己的思考在裡面。
題目描述題目言簡意賅,讓我們把2017這個值傳遞給伺服器。
考察點write-upwhat year is this?所以第一反應是直接給year參數賦值為2017:
?year=2017
然而結果如下:
有提示了,說明year這個參數是對的,但是2017中不可以出現7,這裡如果不了解php精度的話,肯定是對2017進行各種編碼繞過,但是這裡對編碼也進行過濾了:
所以最後一種可能就是利用PHP精度來繞過:
?year=2016.99999999999
二、類型轉換的缺陷理論PHP提供了is_numeric函數,用來變量判斷是否為數字。PHP弱類型語言的一個特性,當一個整形和一個其他類型行比較的時候,會先把其他類型intval數位化再比。
實踐is_numeric()用於判斷是否是數字,通常配合數值判斷。
案例代碼考察點write-up分析下代碼:首先對GET方式提交的參數id的值進行檢驗。id通過is_numeric函數來判斷是否為數字,如果為數字的話,GG。如果不是數字的話,和665進行比較,id的值大於665的時候輸出flag。
乍看上去又好像不可能這裡,但是如果知道PHP弱類型語言的一個特性,當一個整形和一個其他類型行比較的時候,會先把其他類型intval數位化再比。這個特性的話就可以很好的繞過。
http://localhost/?id=666gg
三、鬆散比較符的缺陷理論php比較相等性的運算符有兩種,一種是嚴格比較,另一種是鬆散比較。
如果比較一個數字和字符串或者比較涉及到數字內容的字符串,則字符串會被轉換成數值並且比較按照數值來進行
嚴格比較符嚴格比較符,會先判斷兩種字符串的類型是否相等,再比較。
=== //全等!== //不全等
鬆散比較符鬆散比較符,會先將字符串類型轉換成相同,再比較。
== //等於!= //不等
PHP 會根據變量的值,自動把變量轉換為正確的數據類型。這一點和C 和 C++ 以及 Java 之類的語言明顯不同。雖然這樣PHP方便了程式設計師,但是隨之而來卻會帶來一些安全性的問題。
由於php對變量自動轉換的特性,這裡面的
$a==$b 與 $c==$d 均為真
所以頁面輸出的結果為:
一個深入的例子下面結合PHP 相等性比較缺陷再解釋下會好懂一點:
var_dump(0=="gg"); //truevar_dump(0==="gg"); //falsevar_dump(1=="gg"); //false
0與gg進行鬆散性質的不嚴格比較,會將gg轉換為數值,強制轉換,由於gg是字符串,轉化的結果是0,所以 輸出 true
0與gg進行嚴格 性質的嚴格比較,這裡的gg是字符串類型,和int類型的0不相等,所以輸出 false
0與gg進行鬆散性質的不嚴格比較,會將gg轉換為數值,強制轉換,由於gg是字符串,轉化的結果是0,不等於1,所以輸出 false
var_dump(1=="1gg"); //true var_dump(1=="gg1"); //false
1與1gg進行鬆散性質的不嚴格比較,這裡1gg被強制轉換為int類型的時候會從字符串的第一位開始做判斷進行轉換,這裡的1gg第一位是1,所以這裡1gg被轉換為1,所以輸出 true
1與gg1進行嚴格 性質的嚴格比較,字符串gg1的第一位不是數字,所以它被強制轉換為0,所以輸出 false
var_dump("0e123" == "0e456"); //truevar_dump("0e123" == "0eabc"); //flase
這裡比較特殊,字符串中出現了0e,PHP手冊介紹如下:
當一個字符串欸當作一個數值來取值,其結果和類型如下:如果該字符串沒有包含'.','e','E'並且其數值值在整形的範圍之內 該字符串被當作int來取值,其他所有情況下都被作為float來取值, 該字符串的開始部分決定了它的值,如果該字符串以合法的數值開始,則使用該數值,否則其值為0。
實踐md5繞過(Hash比較缺陷)南京郵電大學網絡攻防訓練平臺中一道比較經典的md5 collision題,關於這道題目的WriteUp網上很多,但是真正深入分析的少之又少~~
題目描述md5 collision源碼
考察點簡單的PHP代碼審計
PHP弱類型的Hash比較缺陷
write-up從源碼中可以得輸入一個a的參數的變量,a首先不等於QNKCDZO並且a得md5值必須等於QNKCDZO加密後的md5值。 乍一看好像不可能存在這樣的值,但是這裡QNKCDZO加密後的md5值為0e830400451993494058024219903391 這裡是0e開頭的,在進行等於比較的時候,PHP把它當作科學計數法,0的無論多少次方都是零。 所以這裡利用上面的弱類型的比較的缺陷來進行解題:?a=s155964671a
字符串加密後md5為0exxxx的字符串(x必須是10進位數字)列表
| 字符串
| md5
|
| —- | —- |
| QNKCDZO
| 0e830400451993494058024219903391
|
| 240610708
| 0e462097431906509019562988736854
|
| aabg7XSs | 0e087386482136013740957780965295 |
| aabC9RqS | 0e041022518165728065344349536299 |
| s878926199a | 0e545993274517709034328855841020 |
md5()和sha1()對一個數組進行加密將返回 NULL
實踐Boston Key Party CTF 2015: Prudential
題目描述I dont think sha1 isbroken.Prove me wrong.
題目給了一個登陸框:
考察點write-up原始碼給出如下:
分析一下核心登錄代碼如下:
GET類型提交了兩個欄位name和password,獲得flag要求的條件是:
這個乍看起來這是不可能的,但是這裡利用sha1()函數在處理數組的時候由於無法處理將返回NULL可以繞過if語句的驗證,if條件成立將獲得flag。 構造語句如下:
?name[]=a&password[]=b
這裡符合了2個拿到flag的條件:
拿到flag: I_think_that_I_just_broke_sha1
拓展總結經過驗證,不僅sha1()函數無法處理數組,這裡md5()函數也有同樣的問題,在處理數組的時候,都將返回NULL
測試代碼如下:
這裡面的核心代碼如下:
同樣利用md5()函數無法處理數組的這個漏洞,構造get請求拿到flag:
?username[]=a&password[]=b
五、字符串處理函數漏洞缺陷理論用法如下:
int strcmp ( string $str1 , string $str2 )
具體的用法解釋如下:
參數 `str1`第一個字符串。參數 `str2`第二個字符串。如果 `str1` 小於 `str2` 返回 `< 0`;如果 `str1` 大於 `str2` 返回 `> 0`;如果兩者相等,返回 0。
這個函數接受到了不符合的類型,例如數組類型,函數將發生錯誤。但是在5.3之前的php中,顯示了報錯的警告信息後,將return 0 !!!! 也就是雖然報了錯,但卻判定其相等了。
這2個函數都是用來處理字符串的,但是在傳入數組參數後都將返回NULL。
實踐Boston Key Party CTF 2015: Northeastern Univ
題目描述Of course, a timing attack might be the answer, but Im quite sure that you can do better than that. 題目給了一個登陸框:
考察點write-up給出原始碼如下:
分析一下核心登錄代碼如下:
if (strcmp($_GET['password'], $flag) == 0)
這裡使用了==鬆散比較了$flag和通過GET方式提交的password的值,如果想等的話,拿到flag。這裡用的是==鬆散性質的比較,再利用字符串處理數組時將會報錯,在5.3之前的php中,顯示了報錯的警告信息後,將return 0。所有這裡將password參數指定為數組,利用函數漏洞拿到flag:
除了strcmp()函數外,ereg()和strpos()函數在處理數組的時候也會異常,返回NULL。測試代碼如下:
將參數password賦值一個數組傳遞進去:
http://localhost/?password[]=gg
ereg()函數是處理字符串的,傳入數組後返回NULL,NULL和 FALSE,是不恆等(===)的,滿足第一個if條件;而strpos()函數也是處理字符串的,傳入數組後返回NULL,NULL!==FALSE,滿足條件,拿到flag:
parse_str函數的作用就是解析字符串並註冊成變量,在註冊變量之前不會驗證當前變量是否存在,所以直接覆蓋掉已有變量。
void parse_str ( string $str [, array &$arr ] )
str 輸入的字符串。
arr 如果設置了第二個變量 arr,變量將會以數組元素的形式存入到這個數組,作為替代。
測試代碼:
考察點write-up找到核心代碼:
因為這裡用到了parse_str函數來傳遞b,if的語句的條件是拿$a[0]來比較的,有因為這裡的變量a的值已經三是固定的了:
$a = "www.sqlsec.com";
這裡其實是我博客的地址~~ 不過不重要。 整體代碼乍看起來又不可能,但是利用變量覆蓋函數的缺陷這裡可以對a的變量進行重新賦值,後面的的if語句再利用本文前面提到的md5()比較缺陷進行繞過:
http://localhost/?b=a[0]=240610708
參考文獻PHP 比較運算符
PHP Float 浮點型
PHP 類型比較表
PHP 弱類型總結
PHP Hash比較存在缺陷,影響大量Web網站登錄認證、忘記密碼等關鍵業務
PHP代碼審計片段講解(入門代碼審計、CTF必備)
淺談PHP弱類型安全
NJCTF2017 線上賽 web 題解
CTF之PHP黑魔法總結
Some features of PHP in CTF
PHP浮點數運算精度的問題
php strcmp()漏洞
危險的is_numeric——PHPYun 2015-06-26 二次注入漏洞分析
【代碼審計】變量覆蓋漏洞詳解
*本文作者:國光,轉載請註明FreeBuf.COM