本文内容参考自《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
Hash 比较缺陷
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)
bool 比较缺陷
当使用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 比较缺陷
当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('错误的数据类型!');
}
....
Array 数组比较缺陷
在使用 in_array
或 array_search
函数时, 如果没有设置参数$strict为true, 则他们将使用松散比较来判断$needle是否是在$haystack中。
参考我前面的文章 https://my.oschina.net/abensky/blog/4298905
来源:oschina
链接:https://my.oschina.net/abensky/blog/4311429