很多人觉得,只要后台能登录,网站就安全。可现实是,一个不起眼的文件上传功能,可能比密码还危险。比如你开发了个图片上传功能,用户传个头像,系统自动保存。看起来没问题,但如果没做防护,有人上传一个名字叫 malicious.php 的文件,内容却是恶意代码,服务器一执行,数据库、用户信息全被拿走。
为什么上传功能这么容易出事?
因为很多开发者只做了前端限制。比如用 JavaScript 检查后缀名,只允许 .jpg、.png。但这种检查太容易绕过了。攻击者用抓包工具改一下请求,把 .php 文件伪装成 .jpg,直接发到服务器。如果后端不做验证,这个 PHP 脚本就会被当成普通文件存下来,一旦访问,立刻执行。
光看后缀名根本没用
有人以为在后端检查文件后缀就行。比如判断是否以 .php 结尾,是就拒绝。但这招也防不住高手。攻击者可以上传 shell.pHp 或 shell.php.jpg,利用大小写或双重扩展名绕过。更狠的是,有些服务器配置不当,会把 .phar、.phtml 也当 PHP 执行。所以只靠后缀黑名单,迟早出事。
真正该怎么做?
第一步,别让用户上传的文件直接通过 URL 访问。最安全的做法是把文件存在 Web 目录之外。比如你的网站根目录是 /var/www/html,那上传的文件应该存到 /var/uploads。这样就算上传了 PHP 脚本,也无法通过浏览器直接访问执行。
第二步,用 MIME 类型验证文件内容。PHP 提供了 finfo_file() 函数,能检测文件真实类型。比如一张图片,即使改了后缀,它的 MIME 还是 image/jpeg。如果检测出来是 text/x-php,那就肯定是脚本,直接拦下。
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['upload']['tmp_name']);
finfo_close($finfo);
if ($mimeType !== 'image/jpeg' && $mimeType !== 'image/png') {
die('只允许上传图片');
}
还有个隐藏陷阱:文件名处理
用户上传的文件名不能直接用。比如有人传个 ;evil.php,或者包含路径的 ../config.php,可能引发路径遍历或命令注入。正确的做法是重命名文件,用时间戳加随机字符串生成新名字,比如 20240405123456_abc123.jpg,彻底切断原名的影响。
最后一步:设置服务器权限
就算文件被传上去了,也能补救。可以在服务器配置中,禁止某个目录执行 PHP 脚本。比如 Nginx 中这样写:
location /uploads/ {
location ~ \.php$ {
deny all;
}
}
这样一来,哪怕有 .php 文件躺在 uploads 目录里,服务器也不会执行它,只能当静态文件返回,风险降到最低。
安全不是加个验证码就行的事。一个上传功能,从文件类型、存储位置、命名规则到服务器配置,每个环节都得设防。别等被黑了才后悔——那时候删库跑路的可能就是你自己。