本文内容参考自《PHP安全之道》。
由于PHP的弱数据类型的特性, 造成了其易学和易用的特点。但是PHP在使用等于(==)判断的时候, 不会严格检查变量类型,会进行变量的自动转换,由此造成了一定的安全隐患。
在下面的代码中, 当用户输入的type=a时, 会直接进入支付逻辑:
$type = $_GET['type']; if($type == 0){ echo '进入支付流程'; }else{ echo '其他逻辑'; }
这时我们建议使用全等于(===)来进行逻辑判断。
我们再看几个例子:
var_dump(false == 0); // bool(true) var_dump(false == ''); // bool(true) var_dump(false == '0'); // bool(true) var_dump(0 == '0'); // bool(true) var_dump(0 == '0xxx'); // bool(true) var_dump(0 == 'xxx'); // bool(true)
更多关于PHP的类型比较的资料参考 https://www.php.net/manual/zh/types.comparisons.php
MD4、MD5、SHA-1、SHA-256、SHA-384以及SHA-512,都是比较常见的安全领域的HASH应用。我们再对比Hash字符串的时候常常用到等于(==)、不等于(!=)进行比较。如果Hash值以0e开头, 后面都是数字, 当与数字进行比较时, 就会被解析成科学计数法 0 X 10^n, 会被判断与0相等, 使攻击者可以绕过某些系统逻辑。
var_dump('0e123456789' == 0); // bool(true) var_dump('0e123456789' == '0'); // bool(true)
当密码经过散列计算后可能会以0开头, 看下面的例子:
$user_name = $_POST['user_name']; $password = $_POST['password']; $user_info = getUserInfo($user_name); if($user_info['password'] == md5($password)){ //这里就可能遇到Hash比较缺陷造成系统漏洞 echo '登录成功'; }else{ echo '登录失败'; }
从PHP5.6开始, 提供了hash_equals
函数来比较Hash值。hash_euqals
函数要求2个参数必须是长度相同的字符串,否则返回false。上面的代码应该修改为:
..... if(hash_equals($user_info['password'], md5($password)){ //这里就可能遇到Hash比较缺陷造成系统漏洞 echo '登录成功'; }else{ echo '登录失败'; } .....
当然,使用全等判断也是可以的。
var_dump('0e123456789' === '0'); // bool(false) var_dump('0e123456789' === 0); // bool(false)
当使用json_decode
或unserialize
函数时,部分结构被解析成 bool 类型, 也会造程缺陷。
$arr = ['user'=>true, 'pass'=>true]; $str = json_encode($arr); // {"user":true,"pass":true} $data = json_decode($str, true); if($data['user'] == 'root' && $data['pass'] == 'mypass'){ echo 'login success'; }else{ echo 'login failed'; }
执行结果为: "login success"。 unserialize的例子如下:
$arr = ['user'=>true, 'pass'=>true]; $str = serialize($arr); // a:2:{s:4:"user";b:1;s:4:"pass";b:1;} $data = unserialize($str, true); if($data['user'] == 'root' && $data['pass'] == 'mypass'){ echo 'login success'; }else{ echo 'login failed'; }
执行结果为: "login success"
对于bool比较缺陷, 使用 全等(===)来避免。
php的int和float作为标量(scalar)类型, 都有其最大最小值。
define ('PHP_INT_MAX', 9223372036854775807); define ('PHP_INT_MIN', -9223372036854775808); //从PHP7.2开始, 有定义float的最大最小值 define('PHP_FLOAT_MAX', 1.7976931348623e+308); define('PHP_FLOAT_MIN', 2.2250738585072e-308);
如果变量的值超过规定的范围时将无法计算正确的结果。下面的例子中的$a、$b、$aa、$bb均超出了int的最大值:
$a = 92233720368547758079223372036854775807; $b = 92233720368547758079223372036854775819; $aa = '92233720368547758079223372036854775807'; $bb = '92233720368547758079223372036854775819'; var_dump(intval($a)); // int(0) var_dump(intval($b)); // int(0) var_dump(intval($aa)); // int(9223372036854775807) var_dump(intval($bb)); // int(9223372036854775807) var_dump($a == $b); // bool(true) var_dump($a === $b); // bool(true) var_dump($a % 100); // int(0) var_dump($b % 100); // int(0) var_dump($aa === $bb); // bool(true) var_dump($aa % 100); // int(7) var_dump($bb % 100); // int(7)
下面看一下float类型的例子:
$c = 0.9999999999999999999999999; $d = 0.9999999999999999999999998; $cc = '0.9999999999999999999999999'; $dd = '0.9999999999999999999999998'; var_dump(floatval($c)); // float(1) var_dump(floatval($d)); // float(1) var_dump(floatval($cc)); // float(1) var_dump(floatval($dd)); // float(1)
在实际的业务逻辑中, 比如支付金额、转账金额, 一定要对最大最小值进行判断,避免数据格式不正确或者越界而导致意外。
建议对int、float类型参数进行empty、is_int/is_numeric判断。
当switch中使用case判断数字时, switch会将参数转换为int类型进行比较:
$num = '2hacker'; switch($num){ case 0: echo 'zero'; break; case 1: echo 'one'; break; case 2: echo 'two'; break; default: echo 'defualt'; }
执行的结果是: two
在进入switch之前一定要判断数据的合法性:
.... if(!is_int($num)){ die('错误的数据类型!'); } ....
在使用 in_array
或 array_search
函数时, 如果没有设置参数$strict为true, 则他们将使用松散比较来判断$needle是否是在$haystack中。
如果觉得博客文章对您有帮助,异或土豪有钱任性,可以通过以下扫码向我捐助。也可以动动手指,帮我分享和传播。您的肯定,是我不懈努力的动力!感谢各位亲~