詳解php比較操作符的安全問題
php的比較操作符有==(等于)松散比較,===(完全等于)嚴(yán)格比較,這里面就會(huì)引入很多有意思的問題,本文給大家詳解php比較操作符的安全問題,對(duì)php操作符相關(guān)資料感興趣的朋友一起學(xué)習(xí)吧
php的比較操作符有==(等于)松散比較,===(完全等于)嚴(yán)格比較,這里面就會(huì)引入很多有意思的問題。
在松散比較的時(shí)候,php會(huì)將他們的類型統(tǒng)一,比如說字符到數(shù)字,非bool類型轉(zhuǎn)換成bool類型,為了避免意想不到的運(yùn)行效果,應(yīng)該使用嚴(yán)格比較。如下是php manual上的比較運(yùn)算符表:
例子? ? 名稱? ? 結(jié)果
$a == $b? 等于? TRUE,如果類型轉(zhuǎn)換后 $a 等于 $b。
$a === $b? 全等? TRUE,如果 $a 等于 $b,并且它們的類型也相同。
$a != $b? 不等? TRUE,如果類型轉(zhuǎn)換后 $a 不等于 $b。
$a <> $b? 不等? TRUE,如果類型轉(zhuǎn)換后 $a 不等于 $b。
$a !== $b? 不全等? TRUE,如果 $a 不等于 $b,或者它們的類型不同。
$a < $b? 小與? TRUE,如果 $a 嚴(yán)格小于 $b。
$a > $b? 大于? TRUE,如果 $a 嚴(yán)格大于 $b。
$a <= $b? 小于等于? TRUE,如果 $a 小于或者等于 $b。
$a >= $b? 大于等于? TRUE,如果 $a 大于或者等于 $b。
0x01 安全問題
1 hash比較缺陷
php在處理hash字符串的時(shí)候會(huì)用到!=,==來(lái)進(jìn)行hash比較,如果hash值以0e開頭,后邊都是數(shù)字,再與數(shù)字比較,就會(huì)被解釋成0*10^n還是為0,就會(huì)被判斷相等,繞過登錄環(huán)節(jié)。
root@kali:~/tool# php -r 'var_dump("00e0345" == "0");var_dump("0e123456789"=="0");var_dump("0e1234abc"=="0");'
bool(true)
bool(true)
bool(false)
當(dāng)全是數(shù)字的時(shí)候,寬松的比較會(huì)執(zhí)行盡力模式,如0e12345678會(huì)被解釋成0*10^12345678,除了e不全是數(shù)字的時(shí)候就不會(huì)相等,這能從var_dump("0e1234abc"=="0")可以看出來(lái)。
2 bool 欺騙
當(dāng)存在json_decode和unserialize的時(shí)候,部分結(jié)構(gòu)會(huì)被解釋成bool類型,也會(huì)造成欺騙。json_decode示例代碼:
$json_str='{"user":true,"pass":true}';
$data= json_decode($json_str,true);
if($data['user'] =='admin'&&$data['pass']=='secirity')
{
print_r('logined in as bool'."\n");
}
運(yùn)行結(jié)果:
root@kali:/var/www# php /root/php/hash.php
logined in as bool
unserialize示例代碼:
$unserialize_str='a:2:{s:4:"user";b:1;s:4:"pass";b:1;}';
$data_unserialize= unserialize($unserialize_str);
if($data_unserialize['user'] =='admin'&&$data_unserialize['pass']=='secirity')
{
print_r('logined in unserialize'."\n");
}
運(yùn)行結(jié)果如下:
root@kali:/var/www# php /root/php/hash.php
logined in unserialize
3 數(shù)字轉(zhuǎn)換欺騙
$user_id= ($_POST['user_id']);
if($user_id=="1")
{
$user_id= (int)($user_id);
#$user_id=intval($user_id);
$qry="SELECT * FROM `users` WHERE user_id='$user_id';";
}
$result= mysql_query($qry)ordie('
'. mysql_error() .'
');
print_r(mysql_fetch_row($result));
將user_id=0.999999999999999999999發(fā)送出去得到結(jié)果如下:
Array
(
[0] => 0
[1] => lxx'
[2] =>
[3] =>
[4] =>
[5] =>
)
本來(lái)是要查詢user_id的數(shù)據(jù),結(jié)果卻是user_id=0的數(shù)據(jù)。int和intval在轉(zhuǎn)換數(shù)字的時(shí)候都是就低的,再如下代碼:
if($_POST['uid'] != 1) {
$res=$db->query("SELECT * FROM user WHERE uid=%d", (int)$_POST['uid']);
mail(...);
}else{
die("Cannot reset password of admin");
}
假如傳入1.1,就繞過了$_POST['uid']!=1的判斷,就能對(duì)uid=1的用戶進(jìn)行操作了。另外intval還有個(gè)盡力模式,就是轉(zhuǎn)換所有數(shù)字直到遇到非數(shù)字為止,如果采用:
if(intval($qq) ==='123456')
{
$db->query("select * from user where qq = $qq")
}
攻擊者傳入123456 union select version()進(jìn)行攻擊。
4 PHP5.4.4 特殊情況
這個(gè)版本的php的一個(gè)修改導(dǎo)致兩個(gè)數(shù)字型字符溢出導(dǎo)致比較相等
$ php -r 'var_dump("61529519452809720693702583126814" == "61529519452809720000000000000000");'
bool(true)
3 題外話:
同樣有類似問題的還有php strcmp函數(shù),manual上是這么解釋的,int strcmp ( string $str1 ,
string $str2
),str1是第一個(gè)字符串,str2是第二個(gè)字符串,如果str1小于str2,返回<0,如果str1>str2,返回>0,兩者相等返回0,假如str2為一個(gè)array呢?
$_GET['key'] =array();
$key="llocdpocuzion5dcp2bindhspiccy";
$flag=strcmp($key,$_GET['key']);
if($flag== 0) {
print"Welcome!";
}else{
print"Bad key!";
}
運(yùn)行結(jié)果:
root@kali:~/php# php strcmp.php
PHP Warning:? strcmp() expects parameter 2 to be string, array given in /root/php/strcmp.php on line 13
Welcome!
比較多種類型
運(yùn)算數(shù) 1 類型運(yùn)算數(shù) 1 類型結(jié)果
null或stringstring將NULL轉(zhuǎn)換為 "",進(jìn)行數(shù)字或詞匯比較
bool或null任何其它類型轉(zhuǎn)換為bool,FALSE<TRUE
objectobject內(nèi)置類可以定義自己的比較,不同類不能比較,相同類和數(shù)組同樣方式比較屬性(PHP 4 中),PHP 5 有其自己的說明
string,resource或numberstring,resource或number將字符串和資源轉(zhuǎn)換成數(shù)字,按普通數(shù)學(xué)比較
arrayarray具有較少成員的數(shù)組較小,如果運(yùn)算數(shù) 1 中的鍵不存在于運(yùn)算數(shù) 2 中則數(shù)組無(wú)法比較,否則挨個(gè)值比較(見下例)