常用知识 - 字符串与数字0比较要注意

  • 作者:KK

  • 发表日期:2016.7.20


先看代码:

var_export([
	'a == 0' => 'a' == 0, //true
	'abcd == 0' => 'abcd' == 0, //true
	'中文 == 0' => '中文' == 0, //true
	'a1 == 0' => 'a1' == 0, //true
	'a123456 == 0' => 'a123456' == 0, //true
	
	'===接下来数字开头===' => '============',
	'0a == 0' => '0a' == 0, //true
	'012a == 0' => '012a' == 0, //false
	'012a == 12' => '012a' == 12, //true
	'012a == 0' => '012a' == 0, //false
	'123 == 0' => '123' == 0, //false
	'123 == 123' => '123' == 123, //true
]);

运行结果:

结论:字母开头的字符串 与 数字0 用 == 模糊比较总是true

详解

不管是'a' == 0还是0 == 'a',只要是两个比较值中有一个是数字,而另一个是字符串,那么PHP会先自动将字符串转换成一个数字,然后再与数字比较,由于a这个字符串转成数字后的值是0,所以最终变成了0 == 0结果就成了true


带来的坑

例子:

$age = $_POST['age']; // 8a
if($age <= 0){
	exit('请输入修改的年龄');
}
$db->insert('user', ['age' => $age]);

这样数据库就会构建成一个无效的插入语句报错了

再假设一下如果$age的值如果是8 or 1 = 1,然后执行查找会怎样?

$age = $_POST['age']; // 8a
if($age <= 0){
	exit('请输入修改的年龄');
}
$db->select('user', ['age' => $age]);
//如果没用PDO参数绑定或者其它参数化查询,底层直接简单拼字符串的话绝对会这样:
SELECT * FROM `user` WHERE age = 8 or 1 = 1

//还有的用了框架的人都途方便,这样写:
(new \yii\db\Query())->from('user')->where('age = ' . $age)->one(); //有注入漏洞,其它框架就不举例子了

//这样写where条件才安全
(new \yii\db\Query())->from('user')->where(['age' => $age])->one(); //注入会失败,

你懂的,主要是这个数字开头的注入字符串绕过了比较逻辑

看你还敢不敢随便拼字符串条件?能用数组条件就尽量用数组条件,一般框架的底层都会做好注入过滤和参数化查询这些事情


这只是拿注入攻击来举个例子,不止是注入,光是让你程序跑出错都有很多种可能,试想,一个我们本应预期是数字的值,居然成了字符串然后被流通到后面的环节中,肯定存在很多影响计算结果的可能性,所以在源头就应该确保它是数字

解决办法

  1. 当然是将 == 比较符写成 ===

    但要注意这其实只是第一思考结果,但实际上程序员们都习惯了写==号,所以不能保证所有团队成员或者新加入员工都能这样去写===的严格比较

    并且你也基本没办法每处都检查他的比较符号有没有写对

  2. 将请求参数强制转换

    我的做法通常都是在接收前端参数时就将它强转成int:

    $age = (int)$this->post('age'); //获取$_POST['age'];
    

    这样就算传来字符串也会被转成数字类型,就当它是那个数字处理咯,要不就用is_numerice这些函数进行严格的内容验证,自己看性价比取舍,但这个防御要有!

    下面是我项目中的一些例子,也强制性要求项目成员收参数时必须按照相关类型先强行转换