PHP编码安全之一: 弱数据类型安全

我与影子孤独终老i 提交于 2020-08-17 04:30:45

本文内容参考自《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_decodeunserialize函数时,部分结构被解析成 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_arrayarray_search函数时, 如果没有设置参数$strict为true, 则他们将使用松散比较来判断$needle是否是在$haystack中。

参考我前面的文章 https://my.oschina.net/abensky/blog/4298905

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!