作者:ZERO 所屬團隊:Arctic Shell
參考資料:
http://archimesan.me/2017/12/21/php%E5%BC%B1%E7%B1%BB%E5%9E%8B%E6%BC%8F%E6%B4%9E/
https://www.cnblogs.com/Mrsm1th/p/6745532.html
https://blog.spoock.com/2016/06/25/weakly-typed-security/
0x1::弱類型與強類型
通常語言有強類型和弱類型兩種,強類型指的是強制數據類型的語言,就是說,一個變量一旦被定義了某個類型,如果不經過強制類型轉換,這個變量就一直是這個類型,在變量使用之前必須聲明變量的類型和名稱,且不經強制轉換不允許兩種不同類型的變量互相操作。我們稱之為強類型,而弱類型可以隨意轉換變量的類型例如可以這樣:
$text=1; $text=”string”
也就是說php並不會驗證變量的類型,可以隨時的轉換類型,估計開發者的意圖是讓程序員可以進行更高效的開發,所以在大量內置函數以及基本結構中使用了很多松散的比較和轉換,防止程序中的變量因為程序員的不規范而報錯,雖然提升了效率,但是引發了很多安全問題。
類型轉換問題
類型轉換最常見的就是int轉String,String轉int。
Int轉String:
$num = 5;
方式1:$item = (string)$num;
方式2:$item = strval($num);
String轉int:
intval() 函數。(取整函數)
主要問題就出現在這個intval()函數上了。
例子:
var_dump(intval(4))//4 var_dump(intval(‘1asd’))//1 var_dump(intval(‘asd1’))//0
上面三個例子說明了intval()函數在轉換字符串的時候即使碰到不能轉換的字符串的時候它也不會報錯,而是返回0。
例子(來自於大佬的博客http://archimesan.me/2017/12/21/php%E5%BC%B1%E7%B1%BB%E5%9E%8B%E6%BC%8F%E6%B4%9E/)
<?php if($_GET[id]) { mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS); mysql_select_db(SAE_MYSQL_DB); $id = intval($_GET[id]); $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'")); if ($_GET[id]==1024) { echo "<p>no! try again</p>"; } else{ echo($query[content]); } } ?>
主要問題就是你輸入一個1024.1這樣就可以利用取整性質進行繞過了。
0x2:比較操作符
在編程中類型轉換是不可避免的一個事情,比如說網絡編程中get方法或者post方法傳入一個需要轉換成int的值,再比如說變量間進行比較的時候,需要將變量進行轉換,鑒於php是自動進行類型轉換,所以會引發很多意想不到的問題。
“= =”與“= = =”比較操作符問題
php有兩種比較方式,一種是“= =”一種是“= = =”這兩種都可以比較兩個數字的大小,但是有很明顯的區別。
“= =”:會把兩端變量類型轉換成相同的,在進行比較。
“= = =”:會先判斷兩端變量類型是否相同,在進行比較。
這里明確說明,在兩個相等的符號中,一個字符串與一個數字相比較時,字符串會轉換成數值。
例如:
<?php var_dump("name"==0); //true var_dump("1name"==1); //true var_dump("name1"==1) //false var_dump("name1"==0) //true var_dump("0e123456"=="0e4456789"); //true ?>
觀察上述代碼,很有意思,按照我們前面講的規則,name與0相比較的時候,因為name是字符串,所以說轉換成數字如果是0的話,0與0相比較自然是true,那末問題來了,下一句中的1name正常來說也是字符串,按照上一句的成立方式,1name應該是0,與1相比較的時候應該為false才對,為什莫為true了呢?我們可以假設一下,帶數字的字符串不會變成0,會變成1,這樣前三條邏輯就解釋的清楚了,但是到第四條就又錯了,為此我查了一下php的官方文檔,文檔是這樣說的:當一個字符串當作一個數值來取值,其結果和類型如下:如果該字符串沒有包含'.','e','E'並且其數值值在整形的范圍之內,該字符串被當作int來取值,其他所有情況下都被作為float來取值,該字符串的開始部分決定了它的值,如果該字符串以合法的數值開始,則使用該數值,否則其值為0。
上述的代碼為什莫出現那種奇怪的情況也就解釋的清楚了。當不同類型的變量進行比較的時候就會存在變量轉換的問題,在轉換之后就有可能會存在問題。
hash比較操作符問題
在hash比較的時候也會出現問題例如:
"0e132456789"=="0e7124511451155" //true "0e1abc"=="0" //true4219903
當出現xex模式的時候代表科學計數法,比如1e3=1*10三次方,在進行比較運算時,如果遇到了0e\d+(意思就是0e就是0e,d+的意思是后面全部是數字)這種字符串,就會將這種字符串解析為科學計數法。所以上面例子中2個數的值都是0因而就相等了。如果不滿足0e\d+這種模式就不會相等。這個題目在攻防平台中的md5 collision就有考到。
例子:《MD5碰撞》源於南郵攻防平台題目(https://cgctf.nuptsast.com/challenges#Web)
<?php if (isset($_GET['Username']) && isset($_GET['password'])) { $logined = true; $Username = $_GET['Username']; $password = $_GET['password']; if (!ctype_alpha($Username)) {$logined = false;} if (!is_numeric($password) ) {$logined = false;} if (md5($Username) != md5($password)) {$logined = false;} if ($logined){ echo "successful"; }else{ echo "login failed!"; } } ?>
這一段代碼的大致意思是輸入一個數字和一個字符串,並且讓他們的MD5值相同,才可以得到successful, 上文提到過,0e在比較的時候會將其視作為科學計數法,所以無論0e后面是什么,0的多少次方還是0。所以我們只需要輸入一個數字和字符串進行MD5加密之后都為0e的即可得出答案。md5('240610708') == md5('QNKCDZO')成功繞過!。
十六進制轉換問題
首先我們看一下例子:
"0x1e240"=="123456" //true "0x1e240"==123456 //true "0x1e240"=="1e240"//false
php在接受一個帶0x的字符串的時候,會自動把這行字符串解析成十進制的再進行比較,0x1e240解析成十進制就是123456,並且與字符串類型的123456和int型的123456都相同。
例子:《起名字真難》源自源於南郵攻防平台題目(https://cgctf.nuptsast.com/challenges#Web)
<?php function noother_says_correct($number) { $one = ord('1'); $nine = ord('9'); for ($i = 0; $i < strlen($number); $i++) { $digit = ord($number{$i}); if ( ($digit >= $one) && ($digit <= $nine) ) { return false; } } return $number == '54975581388'; } $flag='*******'; if(noother_says_correct($_GET['key'])) echo $flag; else echo 'access denied'; ?>
題目大致的意思就是輸入一串key,key呢不可以是數字的形式,但是卻要求與54975581388相等,看完題目就知道要求字符串和數字進行比較,想到的就是弱類型,54975581388與之匹配的十六進制的字符串是0xccccccccc。這就很巧了,全不是數字,自然就繞過了,得到flag。
布爾值轉換問題
舉例:
<?php If ( true=“name”){ echo “success”; }
布爾值可以和任何字符串相等。
0x3:總結
1、字符串和數字比較,字符串會被轉換成數字。
2、混合字符串轉換成數字,看字符串的第一個。
3、字符串開頭以xex開頭,x代表數字。會被轉換成科學計數法(注意一定要是0e/d+的模式)。但是也有例外如:-1.3e3轉換為浮點數是-1300。
4、0x開頭的字符串會先解析成十六進制再進行比較
5、布爾值跟任意字符串都弱類型相等。
php內置函數的參數的松散性
主要意思就是php內部函數在調用時給函數傳遞函數無法接受的參數類型但是卻沒有報錯的情況
json繞過(這個不符合松散型)
首先我們介紹一下什莫是json:JSON概念很簡單,JSON 是一種輕量級的數據格式,他基於 javascript 語法的子集,即數組和對象表示。由於使用的是 javascript 語法,因此JSON 定義可以包含在javascript 文件中,對其的訪問無需通過基於 XML 的語言來額外解析。
例子:
<?php if (isset($_POST['message'])) { $message = json_decode($_POST['message']); $key ="*********"; if ($message->key == $key) { echo "flag"; } else { echo "fail"; } } else{ echo "~~~~"; } ?>
輸入一個數組進行json解碼,如果解碼后的message與key值相同,會得到flag,主要思想還是弱類型進行繞過,我們不知道key值是什莫,但是我們知道一件事就是它肯定是字符串,這樣就可以了,上文講過,兩個等號時會轉化成同一類型再進行比較,直接構造一個0就可以相等了。最終payload message={"key":0}。
MD5 ,sha1繞過
首先還是介紹一下這兩個函數,這倆都是加密函數,分別進行的時給字符串進行MD5加密和計算字符串的 SHA-1 散列。
但是這個函數都有着缺陷,就是不能處理數組。
這樣就很容易繞過了
例子:
<?php if (isset($_POST['a']) and isset($_POST['b'])) { if ($_POST['a'] != $_POST['b']) if (md5($_POST['a']) === md5($_POST['b'])) die('Flag: '.$flag); else print 'Wrong.'; } ?>
直接構造數組就可以繞過了payload: a[]=1&b[]=2
switch繞過
缺陷原理相同,繞過姿勢相同,如果switch是數字類型的case的判斷時,switch會將其中的參數轉換為int類型。如下:
<?php $i ="3name"; switch ($i) { case 0: case 1: case 2: echo "this is two"; break; case 3: echo "flag"; break; } ?>
strcmp繞過這個時候程序輸出的是,類型轉換的i,結果為3返回flag
strcmp()函數在PHP官方手冊中的描述是int strcmp ( string $str1 , string $str2 ),需要給strcmp()傳遞2個string類型的參數。如果str1小於str2,返回-1,相等返回0,否則返回1。strcmp函數比較字符串的本質是將兩個變量轉換為ascii,然后進行減法運算,然后根據運算結果來決定返回值。
例子:
<?php $password="*************** if(isset($_POST['password'])){ if (strcmp($_POST['password'], $password) == 0) { echo "Right!!!login success";n exit(); } else { echo "Wrong password.."; } ?>
在這個題目中我們需要自己輸入一個password的值和$password相比較但是我們不知道這個password的值,有可能時字符串有可能時數字,這個時候怎末辦呢,依然時相同的繞過姿勢,試一試數組繞過假設如果傳入一個數組會怎末樣呢?我們傳入password[]=xxx ,繞過成功。
原理是因為函數接受到了不符合的類型,將發生錯誤,函數返回值為0,所以判斷相等。
array_search()、in_array()繞過
首先介紹一下什莫是array_search()函數, array_search() 函數在數組中搜索某個鍵值,並返回對應的鍵名。in_array() 函數搜索數組中是否存在指定的值。基本功能是相同的,也就是說繞過姿勢也相同。Array系列有兩種安全問題,一種是正常的數組繞過,一種是“= =”號問題。先講第一個數組繞過。
舉例:
<?php if(!is_array($_GET['test'])){exit();} $test=$_GET['test']; for($i=0;$i<count($test);$i++){ if($test[$i]==="admin"){ echo "error"; exit(); } $test[$i]=intval($test[$i]); } if(array_search("admin",$test)===0){ echo "flag"; } else{ echo "false"; } ?>
這段代碼的意思就是先判斷是不是數組,然后在把數組中的內容一個個進行遍歷,所有內容都不能等於admin,類型也必須相同,然后轉化成int型,然后再進行比較如果填入值與admin相同,則返回flag,如何繞過呢?
基本思路還是不變,因為用的是三個等於號,所以說“= =”號這個方法基本不能用,那就用第二條思路,利用函數接入到了不符合的類型返回“0”這個特性,直接繞過檢測。所以payload:test[]=0。
第二個 “= =”的問題
在PHP手冊中,in_array()函數的解釋是bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ),如果strict參數沒有提供或者是false(true會進行嚴格的過濾),那么in_array就會使用松散比較來判斷$needle是否在$haystack中。當strince的值為true時,in_array()會比較needls的類型和haystack中的類型是否相同。
例子:
$array=[0,1,2,'3']; var_dump(in_array('abc', $array)); //true var_dump(in_array('1bc', $array)); //true
通過例子我們就知道了,這個松散的判斷就是等於號,所以出現了“= =”號的特性“abc”==0、“1bc”==1,如果不加true的話就可以利用“= =”輕松繞過。array_search同理。
0x4:結束—時刻防備弱類型
作為一個程序員,弱類型確實提升了程序員書寫代碼的效率,但是也帶來了嚴重的安全問題,可以說一切輸入都是有害的,一切輸入的類型也是可疑的,永遠都要帶有懷疑精神去看待弱類型的php下任何比較函數,任何數學運算!