PHP 中与或运算符优先级导致的安全问题


前言

在 PHP 中,&&|| 是逻辑运算符,分别用于逻辑与(AND)和逻辑或(OR)操作。而 and 和 or 也是逻辑运算符,执行相同的基本逻辑操作,但不同之处在于它们的优先级。
简单来说,&&|| 的优先级较高,而 and 和 or 的优先级较低。
这意味着,在表达式中,&&|| 会先于其他低优先级的运算符(例如赋值运算符 =)进行计算,而 and 和 or 会后于其他高优先级的运算符进行计算。

举例

$result = true || false;
// 结果是 true,因为 || 的优先级高于 =,所以先执行 true || false,然后将结果(true)赋值给 $result

$result = true or false;
// 结果也是 true,但与上面的计算不同。因为 or 的优先级低于 =,所以这里实际上是先将 true 赋值给 $result,然后执行 false,但 false 的值没有被用到。

实际应用中有如下一段文件上传代码的判断检查(假设文件名为 a.php):

$file = $_FILES["file"];
$allowedExts = ["gif", "jpeg", "jpg", "png", "webp"];
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if ($extension == "") {
    exit("<script language=\"JavaScript\">;alert(\"未选择图片!\");location.href=\"../setup.php\";</script>;");
}
if ($file["type"] == "image/jpeg" || $file["type"] == "image/png" || $file["type"] == "image/jpg" || $file["type"] == "image/gif" || $file["type"] == "image/webp" && in_array($extension, $allowedExts)) {
} else {
    exit("<script language=\"JavaScript\">;alert(\"文件类型错误,请上传图片!\");location.href=\"../setup.php\";</script>;");
}
if ($file["size"] > 5242880) {
    exit("<script language=\"JavaScript\">;alert(\"请上传5MB以内的图片!\");location.href=\"../setup.php\";</script>;");
}

代码的本意是判断上传的文件类型是否为 jpeg、png、jpg、gif 或 webp当中的一种.且同时需要文件名后缀也是其中的一中.
但问题就出在 if 条件最后的 && 判断上, 因为 && 的优先级高于 ||,这意味这个条件语句中,&& 操作会首先被计算,这就导致逻辑错误。
实际上的执行顺序如下:

  1. 检查 $file["type"] 是否等于 "image/jpeg",或者 "image/png",或者 "image/jpg",或者 "image/gif"
  2. 然后检查 $file["type"] 是否等于 "image/webp" 并且 $extension 是否在 $allowedExts 数组中。

由于没有圆括号将所有的文件类型检查组合在一起,这个条件实际上只在 $file["type"]"image/webp" 并且 $extension 是允许的扩展名时,才要求 $extension 必须在 $allowedExts 数组中。对于其他文件类型,即使扩展名不在允许的列表中,代码也会判断为有效。

考虑到上传的文件名为 a.php,根据这段代码的逻辑,$extension 将会是 php,这不在 $allowedExts 数组中。然而,由于 $file["type"] 可能被客户端错误地设置为任何上述的有效图片MIME类型,这样的文件会被错误地接受,因为扩展名检查只对 "image/webp" 类型的文件生效。

实验如下

<?
$name = "a.php";
$type = "image/png";
$allowedExts = ["gif", "jpeg", "jpg", "png", "webp"];
$temp = explode(".", $name);
$extension = end($temp);
echo $extension;
echo "\n";
if($type == "image/png" || $type == "image/jpg" && in_array($extension, $allowedExts)){
// if($type == "image/png" || $type == "image/jpg" and in_array($extension, $allowedExts)){
// if(($type == "image/png" || $type == "image/jpg") && (in_array($extension, $allowedExts))){
    echo "OK\n";
}else{
    echo "Bad\n";
}

这里利用 https://onlinephp.io/ 来进行快速的测试验证

PHP 中与或运算符优先级导致的安全问题

可以看到结果是 OK,即验证通过,但是文件名后缀却是 php ,违背了本意.

我给出的测试demo有两个注释的修正示例,结果是符合初衷的:

PHP 中与或运算符优先级导致的安全问题

适当的修改为类似如下即可避免此种逻辑错误.

$file = $_FILES["file"];
$allowedExts = ["gif", "jpeg", "jpg", "png", "webp"];
$temp = explode(".", $file["name"]);
$extension = strtolower(end($temp)); // 将扩展名转换为小写

// 确保选择了文件
if ($extension == "") {
    die("未选择图片!");
}

// 检查文件扩展名和MIME类型
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file["tmp_name"]);
$allowedMimeTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];

if (in_array($extension, $allowedExts) && in_array($mime, $allowedMimeTypes)) {
    // 文件类型和扩展名都有效
    // 继续处理文件上传...
} else {
    die("文件类型错误,请上传图片!");
}

总结

  • && 运算符和 || 运算符都是用于将多个条件连接起来,判断整个条件表达式的真假。
  • && 运算符要求所有连接的条件同时为真,只要有一个条件为假,整个表达式就为假。
  • || 运算符只要有一个条件为真,整个表达式就为真。
  • 当使用 && 运算符时,如果第一个条件为假,PHP就会停止执行后面的条件判断,因为已经可以确定整个表达式的结果为假。
  • 当使用 || 运算符时,如果第一个条件为真,PHP也会停止执行后面的条件判断,因为已经可以确定整个表达式的结果为真。

总结:&& 运算符要求所有条件都为真,|| 运算符只要有一个条件为真。根据不同的条件逻辑,选择合适的运算符来构建条件表达式,以实现逻辑判断。



扫描二维码,在手机上阅读

推荐阅读:

.NET RSA 算法的 XML 格式简介及其转换 PEM 格式

通过搜索引擎和空间测绘工具来查看各地开放摄像头

评 论
更换验证码
avatar
人大代表
  • Chrome
  • Windows10
24年的新闻,你22年爆出来了。厉害了,大哥。
2024-03-28 22:36 回复
avatar
  • Chrome
  • Mac10.15.7
@人大代表:卧槽 别搞我...
2024-03-28 22:38 回复
avatar
人大代表
  • Chrome
  • Windows10
@admin:坦白从宽,你懂的。
2024-03-28 22:54 回复
avatar
admin
  • Chrome
  • Mac10.14.6
@人大代表:我不知道 也不评论
2024-04-04 09:25 回复