PHP弱类型

做题时经常碰到PHP弱类型相关的知识,把以前写的重新总结整理下

自动转换

1.16进制、科学计数法,会被自动转为十进制数:

1
2
3
4
5
6
7
8
9
10
<?php

$a = 0x61;
$b = 1e5;

echo $a; //输出 97
echo "\n";
echo $b; //输出 100000

?>

2.字符串进行数学运算时,会自动转换为数字后再进行运算(如果字符串首位为非数字,则会被转换为0,若为数字,则会从前往后一直转换,直到碰到非数字):

1
2
3
4
5
6
<?php

$a = '123.5a';
echo $a; //输出 123.5a
echo "\n";
echo $a+1; //输出 124.5

==

1.当数字与其他类型弱比较时,会把其他类型转换为数字之后再比较(转换方式同上):

1
2
3
4
5
6
7
<?php

if(123 == '123abcd'){
echo 'good';
}
//输出 good
?>

2.NULL、FALSE、0三者弱相等:

1
2
3
4
5
6
7
<?php

if(NULL == FALSE){
echo 'good';
}
//输出 good
?>

数据类型比较

strcmp

int strcmp( string $str1, string $str2)

比较两个字符串:

  • 如果两个字符串相等,返回0
  • 若第一个字符串的ASCII码大于第二个字符串,返回1,否则返回-1,其中,如果一个字符串是另一个字符串的一部分,则会返回两个字符串长度相减的值

strcmp处理数组,会返回NULL

ereg

正则表达式匹配

int ereg( string $pattern, string $string[, array &$regs] )

  • 如果找到与 pattern 中圆括号内的子模式相匹配的子串并且函数调用给出了第三个参数 regs,则匹配项将被存入 regs 数组中
  • 如果在 string 中找到 pattern 模式的匹配则返回所匹配字符串的长度,如果没有找到匹配或出错则返回 FALSE;如果没有传递入可选参数 regs 或者所匹配的字符串长度为 0,则本函数返回 1。

ereg函数存在NULL截断漏洞,当ereg读取字符串string时,如果遇到了%00,后面的字符串就不会被解析。

ereg函数处理数组,会返回NULL

strpos

查找字符串首次出现的位置

int strpos( string $haystack, mixed $needle[, int $offset = 0] )

  • haystack 在该字符串中进行查找。
  • needle 如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符的顺序值。
  • offset 如果提供了此参数,搜索会从字符串该字符数的起始位置开始统计。如果是负数,搜索会从字符串结尾指定字符数开始。
  • 如果没找到 needle,将返回 FALSE。

strpos处理数组,会返回NULL

md5,sha1

md5函数与sha1函数处理数组,会返回NULL

1.数组绕过

2.0e绕过(注意0e后要为纯数字)

3.强碰撞

例如:

if ($md5==md5($md5)) 

此时$md5可以为:

0e215962017

它的md5值为:

0e291242476940776845150308577824

intval

int intval( mixed $var[, int $base = 10] )

通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

1.intval在转换字符串时,会从前往后转换,直到碰到了一个非数字字符

1
2
3
4
intval('123abc'); //输出 123
intval('a123'); //输出0
intval('0x7e5') //输出0
intval('1e10') //输出1

成功时返回 var 的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。

例如:

if(intval($num) < 2020 && intval($num + 1) > 2021){  

此时$num可用16进制或者科学计数法绕过

2.最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。

在判断非回文字符串的intval是回文,可以运用用此知识

is_numeric

检测变量是否为数字(包括16进制)或数字字符串;

数字字符串前面的空格可以忽略(后面有空格不会忽略):

1
2
3
4
5
6
7
<?php

if(is_numeric(' 1')){
echo 'good';
}
//输出 good
?>

如果有两个is_numeric判断的时候用and连接起来,and后面的is_numberic判断可忽略:

1
2
3
4
5
6
7
8
9
<?php

$c = is_numeric('1') and is_numeric('a');

if($c){
echo 'good';
}
//输出 good
?>

switch

switch没有break时会一直往下执行,直到遇到break

switch()存在弱类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

switch (0x02) {
case 0:
echo "0";
break;
case 1:
echo "1";
break;
case 2:
echo "2";
break;
}
//输出 2
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

switch (true) {
case 0:
echo "0";
break;
case 1:
echo "1";
break;
case 2:
echo "2";
break;
}
//输出 1
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

switch ('a') { // null、false
case 0:
echo "0";
break;
case 1:
echo "1";
break;
case 2:
echo "2";
break;
}
//输出 0
?>

浮点数的精度

官方文档:

浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。

此外,以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…。

所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数。


例:

if(!(intval($a2)<1024 and intval($a2+1)>1024)) exit("emmmmm");

此时构造:

a2 = 1023.9999999999999

即可绕过