数码生活屋
白蓝主题五 · 清爽阅读
首页  > 远程协作

PHP脚本上传安全:别让一张图片毁了你的网站

前几天朋友老张找我救急,说他做的企业官网突然被挂了黑页,访问直接跳博彩。查了一圈发现,问题出在用户头像上传功能上。他用的是最基础的 PHP 文件上传处理,没做任何校验,结果被人传了个伪装成图片的 PHP 木马,一访问就执行。

上传功能不是漏洞,但写法不对就是后门

很多开发者觉得,用户能传个文件而已,能有多大风险?可现实是,攻击者会把恶意脚本改成 .jpg 后缀,或者利用图片 EXIF 信息藏代码。一旦服务器没识别出来,直接保存并允许执行,等于亲手给黑客开了扇门。

最常见的翻车点,就是只靠前端 JavaScript 检查文件类型。比如用 accept="image/jpeg" 或判断文件扩展名。但这根本防不住——工具改个包,发个 POST 请求就能绕过。

真正该做的几件事

第一步,永远不要相信客户端传来的文件信息。$_FILES['file']['type'] 这个 MIME 类型是可以伪造的,不能作为判断依据。

应该用 PHP 的 getimagesize() 函数去读文件真实结构。比如:

$info = getimagesize($_FILES['file']['tmp_name']);
if (!$info) {
    die('不是有效图像');
}

第二步,限制上传目录的执行权限。比如把用户上传的文件放在 /uploads/ 目录,并在 Nginx 配置中禁止该目录运行 PHP:

location ~* /uploads/.*\.php$ {
    deny all;
}

这样就算有 .php 文件混进去了,也执行不了。

文件名也要小心处理

别直接用用户上传的文件名,容易遇到覆盖系统文件、路径穿越等问题。推荐做法是重新生成随机文件名:

$uploadDir = '/var/www/html/uploads/';
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . strtolower($extension);
$targetPath = $uploadDir . $safeName;

同时过滤掉可能危险的扩展名,比如 .php、.phtml、.php5 等,即使重命名了也不能留。

加个二次验证更安心

对于关键系统,还可以用 finfo 扩展检查文件魔术字节(magic bytes):

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);

if (!in_array($mimeType, ['image/jpeg', 'image/png', 'image/gif'])) {
    die('不支持的文件类型');
}

这招能识别出“假装是图片”的 PHP 脚本,因为真正的 JPEG 文件开头是 FF D8 FF,而 PHP 是 <?php 开头,一比对就知道。

远程协作项目里,团队成员常要共享文档、头像、附件,这些上传入口就像小区的快递柜——方便大家,但也可能被人塞进不该进的东西。多花半小时加固上传逻辑,远比事后清木马、恢复数据来得划算。