在 PHP 中处理字符串和数字比较时需要小心谨慎的重要性,尤其是在安全敏感的应用中。
在某些情况下,PHP 的类型强制转换可能会导致意外和潜在的安全漏洞。
var_dump("admin" == 0); //true
var_dump("1admin" == 1); //true
var_dump("admin1" == 1); //false
var_dump("admin1" == 0); //true
var_dump("0e123456" == "0e4456789"); //true
这段代码展示了 PHP 中的一些有趣且可能导致安全隐患的类型强制转换行为。让我们逐一分析每个表达式:
var_dump("admin" == 0);
在 PHP 中,当一个字符串与数字进行比较时,如果字符串不是以数字开始,则该字符串会被转换为数字 0。因此,“admin” 被转换为 0,所以 0 == 0 是 true。
var_dump("1admin" == 1);
在这个例子中,字符串 “1admin” 在尝试转换为数字时,会被解析为数字 1,因为它以 1 开始,忽略后续非数字字符。因此,1 == 1 是 true。
var_dump("admin1" == 1);
这里 “admin1” 作为字符串,不能转换为数字 1,因为它不是以数字开始的。它将被转换为 0。所以,0 == 1 是 false。
var_dump("admin1" == 0);
同上,“admin1” 转换为数字时变成了 0,所以 0 == 0 是 true。
var_dump("0e123456" == "0e4456789");
这是 PHP 类型强制转换中最有趣的一个例子。当一个字符串看起来像是科学记数法时(例如 “0e123456”),在与另一个类似结构的字符串比较时,它们都会被转换为数字 0。
这是因为 e 后面的数字被解释为指数,但由于前面的数字是 0,所以整个表达式的值为 0。
因此,两个看似不同的字符串在这种情况下会被认为是相等的。
原理还是科学计数法字符串比较的问题.
var_dump("0e123456789012345678901234567890" === "0"); //false
var_dump("0e123456789012345678901234567890" == "0"); //true
因为 e 后面的数字被解释为指数,但由于前面的数字是 0,所以整个表达式的值为 0。
$pass = $_GET['password'];
$password = '0e342768416822451524974117254469';
if (md5($pass) == $password) {
echo "flag{xx-xx-xxxx-xxx}";
}
else {
echo "error";
}
对于上面的代码那么只要是提交的密码的md5值是以0e
开头的都会绕过, 比如.
?pwd=s1885207154a
除了使用===
进行判断外,从 PHP 5.6
开始,hash_equals()
函数被引入,主要用于安全地比较两个字符串的哈希值,通常用于密码或敏感数据的验证。
hash_equals()
函数的关键特点:这意味着函数在比较两个字符串时所花费的时间不依赖于字符串的内容。这是预防某些类型的定时攻击的重要特性,例如,在一些安全关键的应用场景中,攻击者可能尝试通过测量不同输入导致的响应时间差异来推断信息
。
hash_equals() 安全地处理二进制数据,确保在比较过程中不会因为特殊字符(如 NULL 字节)而意外截断或错误处理。
它专门用于比较字符串,而不是用于其他数据类型。
一个典型例子是在用户认证系统中比较用户输入的密码哈希值与存储在数据库中的哈希值:
$expected = '存储的哈希值';
$provided = hash('sha256', '用户输入的密码');
if (hash_equals($expected, $provided)) {
// 密码匹配
} else {
// 密码不匹配
}
$str = '{"user":true, "pass":true}';
$data = json_decode($str, true);
if($data['user'] == 'root' && $data['pass'] == 'myPass'){
echo '登陆成功 获得flag{xx-ssss-xxxx}';
}else{
echo '登陆失败!';
}
这里用户名和pass都是true
, 由于使用的是==
判断, 两个比较的结果都是true.
$str = 'a:2:{s:4:"user";b:1;s:4:"pass";b:1;}';
$data = unserialize($str);
if($data['user'] == 'root' && $data['pass'] == 'myPass'){
print_r('获得flag{xx-ssss-xxxx}');
}else{
print_r('失败!');
}
a:2:
表示一个数组,包含 2 个元素。
s:4:"user";b:1;
表示数组的第一个键值对。键是一个长度为 4 的字符串 “user”,值是布尔值 true(在 PHP 序列化格式中表示为 b:1)。
s:4:"pass";b:1;
表示数组的第二个键值对。键是一个长度为 4 的字符串 “pass”,值也是布尔值 true。
那么用户名和pass都是true
, 绕过了判断.
$a=98869694308861098395599991222222222222;
$b=98869694308861098395599992999999999999;
var_dump($a === $b);
虽然两个值不同, 但是比较的结果是true.
PHP 中的 switch 语句是松散比较(==)
,而不是严格比较(===)
。这意味着类型强制转换会发生。
$num = '3number';
switch($num){
case 0:
echo '000';
break;
case 1:
echo '111';
break;
case 2:
echo '222';
break;
case 3:
echo '333';
break;
default:
echo "error";
}
当 $num = '3number';
时,由于进行松散比较,PHP 会尝试将这个字符串转换为数字。
由于字符串以数字 3 开始,其余部分在转换过程中将被忽略,因此 $num 被视为数字 3。
结果并不会输出error
, 而是333
.
在 PHP 中,in_array
和 array_search
函数在默认情况下使用松散比较(==)
。
如果没有明确设置 strict
参数为 true
,这些函数会执行类型强制转换,可能导致一些非直观的结果。
$array = ['a', 0, 1, 2, '3'];
var_dump(in_array('abc', $array)); // bool(true)
var_dump(array_search('abc', $array)); // int(1)
in_array('abc', $array)
在比较 'abc'
和数组中的0
时,由于'abc'
在松散比较中会被视为 0(非数字开头的字符串在转换为数字时会成为 0)
,因此 in_array
会返回 true
。
同理,array_search('abc', $array)
在寻找'abc'
时也会找到0
这个元素的位置,因此返回 1
。