pathinfo()仅取最后一个点后字符串为扩展名,如archive.tar.gz返回gz而非tar.gz;其设计遵循posix规范,不解析复合语义,故上传校验不可单靠此逻辑。

只校验 $_FILES['file']['name'] 的后缀名根本没用
攻击者上传 shell.php.jpg,你用 pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION) 拿到的是 jpg,就放行——但 Apache 可能按从右往左规则解析成 PHP;上传 shell.PHP,大小写没统一,白名单里只有 php,照样漏掉。更糟的是 shell.php%00.jpg(PHP 5.3.4 之前)或 shell.php.(Windows 自动去点),pathinfo 解析结果不可靠。
实操建议:
- 必须先用
strtolower()统一小写再取后缀 - 用正则剥离文件名末尾所有点、空格、
%00等干扰字符,再取扩展名 - 白名单显式列出全部允许项:
['jpg', 'jpeg', 'png', 'gif'],不写通配符、不依赖黑名单 - 额外加一层黑名单拦截危险扩展:
php、phtml、php3、php4、php5、phar、.htaccess
finfo_file() 是唯一靠谱的 MIME 校验方式
$_FILES['file']['type'] 是浏览器传来的,改个 Content-Type: image/jpeg 就绕过,完全不能信。真正有效的服务端 MIME 检查得靠 finfo_file() 读取文件头(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('MIME 类型不匹配');
}
注意:
-
finfo_file()仍可能被构造头部绕过(如图片马),所以只能作为辅助手段,不能单独依赖 - 必须配合扩展名白名单 + 文件内容检测(如
getimagesize())一起用 - 如果上传非图片类文件(比如 PDF、DOCX),需对应调整白名单和检测函数
重命名时必须丢弃原始文件名中的所有点和斜杠
攻击者传 ../../.htaccess 或 shell.php.jpg,你重命名为 uniqid().'.jpg' 还是不够——因为没清理原始名里的路径分隔符和多点结构,可能导致目录穿越或 Apache 多后缀解析漏洞。
安全重命名做法:
- 用
basename()剥离路径,再用正则preg_replace('/[./\\ s]+/', '_', $originalName)替换所有危险字符 - 扩展名强制从白名单中取,不拼接原始后缀
- 最终文件名格式:
uniqid() . '_' . $safeName . '.' . $allowedExtension - 上传路径必须硬编码为绝对路径,避免用户可控变量拼入
上传目录必须禁止执行 PHP 脚本
即使前面全做对了,只要上传目录的 Web 服务器配置允许执行 PHP,攻击者上传的合法图片里嵌了一句话(图片马),再配合文件包含漏洞,照样 getshell。
关键配置动作:
- Apache:在上传目录下放
.htaccess,内容为SetHandler default-handler或RemoveHandler .php .phtml - Nginx:在 server 或 location 块中加
location ~ ^/uploads/.*.(php|phtml|php3|php4|php5|phar|htaccess)$ { deny all; } - 最彻底方案:把上传目录放到 Web 根目录之外,用脚本读取并输出(不通过 URL 直接访问)
这步最容易被忽略——很多开发者以为“后端校验严一点就安全了”,其实最后执行权限才是防线终点。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











