CTF中常見的 PHP 弱類型漏洞總結

做者:ZERO    所屬團隊:Arctic Shelljavascript

參考資料:php

http://archimesan.me/2017/12/21/php%E5%BC%B1%E7%B1%BB%E5%9E%8B%E6%BC%8F%E6%B4%9E/html

https://www.cnblogs.com/Mrsm1th/p/6745532.htmljava

https://blog.spoock.com/2016/06/25/weakly-typed-security/mysql


 

0x1::弱類型與強類型

一般語言有強類型和弱類型兩種,強類型指的是強制數據類型的語言,就是說,一個變量一旦被定義了某個類型,若是不通過強制類型轉換,這個變量就一直是這個類型,在變量使用以前必須聲明變量的類型和名稱,且不經強制轉換不容許兩種不一樣類型的變量互相操做。咱們稱之爲強類型,而弱類型能夠隨意轉換變量的類型例如能夠這樣:git

 

 

$text=1; $text=」string

 

 

也就是說php並不會驗證變量的類型,能夠隨時的轉換類型,估計開發者的意圖是讓程序員能夠進行更高效的開發,因此在大量內置函數以及基本結構中使用了不少鬆散的比較和轉換,防止程序中的變量由於程序員的不規範而報錯,雖然提高了效率,可是引起了不少安全問題。程序員

類型轉換問題

 類型轉換最多見的就是int轉String,String轉int。sql

Int轉String:編程

$num = 5;
方式1:$item = (string)$num;
方式2:$item = strval($num);json

 

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:總結

一、字符串和數字比較,字符串會被轉換成數字。

二、混合字符串轉換成數字,看字符串的第一個。

三、字符串開頭以xex開頭,x表明數字。會被轉換成科學計數法(注意必定要是0e/d+的模式)。可是也有例外如:-1.3e3轉換爲浮點數是-1300。

四、0x開頭的字符串會先解析成十六進制再進行比較

五、布爾值跟任意字符串都弱類型相等。

 

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下任何比較函數,任何數學運算!

相關文章
相關標籤/搜索