php弱类型比较

php是弱类型语言,它含有很多隐式的转换。常见的php弱类型,主要分为类型转换问题和内置函数参数的松散性两类。

类型转换问题

类型转换是无法避免的问题。例如需要将GET或者是POST的参数转换为int类型,或者是两个变量不匹配的时候,PHP会自动地进行变量转换。

“ == “ 和 “ === “

1
2
$a == $b ;
$a === $b ;

=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较

== 在进行比较的时候,会先将字符串类型转化成相同,再比较

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行。

1
2
3
4
5
6
7
<?php
var_dump("admin"==0); //true
var_dump("1admin"==1); //true
var_dump("admin1"==1) //false
var_dump("admin1"==0) //true
var_dump("0e123456"=="0e4456789"); //true
?>

在上述代码中,

  1. “admin”==0比较的时候,会将admin转化成数值,强制转化,由于admin是字符串,转化的结果是0自然和0相等。

  2. “1admin”==1比较的时候会将1admin转化成数值1,而”admin1”却被转化成0。

  3. “0e123456”==”0e456789”相互比较的时候,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等。

php手册中解释如下:

当一个字符串当作一个数值来取值,其结果和类型如下:如果该字符串没有包含’.’,’e’,’E’并且其数值值在整形的范围之内,该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0

Hash比较

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

攻击者可以利用这一漏洞,通过输入一个经过哈希后以”0E”开头的字符串,即会被PHP解释为0,如果数据库中存在这种哈希值以”0E”开头的密码的话,他就可以以这个用户的身份登录进去,尽管并没有真正的密码。

1
2
3
"0e132456789"=="0e7124511451155" //true
"0e123456abc"=="0e1dddada" //false
"0e1abc"==0 //true

在进行比较运算时,如果遇到了0e\d+这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中2个数的值都是0因而就相等了。如果不满足0e\d+这种模式就不会相等。

十六进制转换

1
2
3
"0x1e240"=="123456"		//true
"0x1e240"==123456 //true
"0x1e240"=="1e240" //false

当其中的一个字符串是0x开头的时候,PHP会将此字符串解析成为十进制然后再进行比较,0x1e240解析成为十进制是123456,所以与int类型和string类型的123456比较都相等。

类型转换

常见的转换主要就是int转换为string,string转换为int。

int转string:

1
2
3
$var = 5;
方式1:$item = (string)$var;
方式2:$item = strval($var);

string转int:intval()函数。

对于这个函数,可以先看2个例子。

1
2
3
var_dump(intval('2'))	//2
var_dump(intval('3abcd')) //3
var_dump(intval('abcd')) //0

说明intval()转换的时候,会将从字符串的开始进行转换知道遇到一个非数字的字符。即使出现无法转换的字符串,intval()不会报错而是返回0。

官网注释为:

1
2
3
4
5
6
7
8
It seems intval is interpreting valid numeric strings differently between PHP 5.6 and 7.0 on one hand, and PHP 7.1 on the other hand.

<?php
echo intval('1e5');
?>

will return 1 on PHP 5.6 and PHP 7.0,
but it will return 100000 on PHP 7.1.

bool类型的true比较

bool类型的true可以和任意字符串弱类型相等

1
2
3
4
5
6
<?php  
if(true == "GETF"){
echo "OK";
}
?>
#OK[Finished in 0.3s]

json绕过

json是一种轻量级的数据交换格式。它采用完全独立于编程语言的文本格式来存储和表示数据。

在 JS 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型:

  • 对象表示为键值对
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组
1
2
3
4
5
6
7
8
9
10
11
12
<?php
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key)
echo "flag";
else
echo "fail";
}else{
echo "~~~~";
}
?>

输入一个json类型的字符串,json_decode函数解密成一个数组,判断数组中key的值是否等于 $key的值,但是$key的值我们不知道,但是可以利用0==”admin”这种形式绕过

php的json_decode()函数会根据json数据中的数据类型来将其转换为php中的相应类型的数据,也就是说,如果我们在json中传一个string类型,那么该变量就是string,如果传入的是number,则该变量为number。因此,我们如果传入一个数字,就可以使之相等。网页中的表单可能限制了所有的输入都是string,即使输入数字,传入的东西也是

1
{"key":"0"}

这是一个字符串0,我们需要让他为数字类型,用burp拦截,把两个双引号去掉,变成这样:

1
{"key":0}

最终payload message={“key”:0}

内置函数的参数的松散性

md5()绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if (isset($_GET['v1']) && isset($_GET['v2'])) {
$logined = true;
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];

if (!ctype_alpha($v1)) {$logined = false;}
if (!is_numeric($v2) ) {$logined = false;}
if (md5($v1) != md5($v2)) {$logined = false;}

if ($logined){

// continuue to do other things
} else {
echo "login failed"
}
}
?>

题目大意是要输入一个字符串和数字类型,并且他们的md5值相等,就可以成功执行下一步语句。

上文提到过,0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。md5(‘240610708’) == md5(‘QNKCDZO’)成功绕过!

以0e开头的md5值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020

s155964671a
0e342768416822451524974117254469

s214587387a
0e848240448830537924465865611904

s214587387a
0e848240448830537924465865611904

s878926199a
0e545993274517709034328855841020

s1091221200a
0e940624217856561557816327384675

s1885207154a
0e509367213418206700842008763514

当传递一个array时,md5()不会报错,但是会无法正确地求出array的md5值,这样也会导致任意两个array的md5值相等。

strcmp()漏洞绕过 php -v < 5.3

1
2
3
4
5
6
7
8
9
10
<?php 
$password="***************"
if(isset($_POST['password'])){

if (strcmp($_POST['password'], $password) == 0) {
echo "Right!!!login success";n
exit(); } else {
echo "Wrong password..";
}
?>

strcmp是比较两个字符串,如果str1< str2 则返回< 0,如果str1大于str2返回>0, 如果两者相等返回0。strcmp函数比较字符串的本质是将两个变量转换为ascii,然后进行减法运算,然后根据运算结果来决定返回值。

我们是不知道$password的值的,题目要求strcmp判断的接受的值和$password必需相等,strcmp传入的期望类型是字符串类型,如果传入的是个数组会怎么样呢?

1
2
$array=[1,2,3];
var_dump(strcmp($array,'123')); //null,在某种意义上null也就是相当于false。

所以我们传入 password[]=xxx 可以绕过,是因为函数接受到了不符合的类型,将发生错误,但是还是判断其相等。
payload: password[]=xxx(eg:password[]=admin)

switch()

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$a="4admin";
switch ($a) {
case 1: echo "fail1"; break;
case 2: echo "fail2"; break;
case 3: echo "fail3"; break;
case 4: echo "sucess"; //结果输出success;
break;
default: echo "failall";
break;
}
?>

switch()会将参数进行类型转换,在上述代码中将4admin转换成4,于是输出success。

in_array()

在PHP手册中,in_array()函数的解释是bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ),如果strict参数没有提供,那么in_array就会使用松散比较来判断$needle是否在$haystack中。当strince的值为true时,in_array()会比较needls的类型和haystack中的类型是否相同。

1
2
3
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true

可以看到上面的情况返回的都是true,因为’abc’会转换为0,’1bc’转换为1。
array_search()与in_array()也是一样的问题。


参考:
https://www.secpulse.com/archives/69529.html
https://blog.spoock.com/2016/06/25/weakly-typed-security/