双后缀名绕过源于校验与解析逻辑不一致:服务端仅取最后一个点后的扩展名(如jpg)放行,而旧版apache从右往左解析,将shell.php.jpg当作php执行。

双后缀名绕过是怎么发生的
攻击者上传 shell.php.jpg 这类文件,服务端若只取最后一个点后的扩展名(如用 pathinfo($filename, PATHINFO_EXTENSION)),就会得到 jpg,误判为安全图片。而某些旧版 Apache + mod_php 组合在解析时会“从右往左”匹配后缀,最终把 .php.jpg 当作 PHP 执行——本质是解析器与校验逻辑不一致。
只校验最后一个后缀就是最大的坑
常见错误写法:$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);,它对 a.php.jpg、b.PHP.png、c.php. 全部返回 jpg、png、 ,完全失去防御意义。
- 必须完整提取并清洗原始文件名,不能依赖
PATHINFO_EXTENSION单点判断 - 用
basename()剥离路径后,再用正则或explode('.', $clean_name)拆分所有点号段 - 若拆出超过 2 个非空段(如
['a', 'php', 'jpg']),直接拒绝——合法图片名极少含多个点 - 对所有段统一转小写,并检查是否包含危险词(
php、asp、jsp、sh、htaccess)
finfo_file() 是基础,但不能代替后缀清理
finfo_file() 能识别真实 MIME 类型,但它对双后缀文件(如 shell.php.jpg)仍可能返回 image/jpeg——因为文件头确实是 JPG 头。这意味着:即使 MIME 校验通过,也必须确保扩展名本身不带隐藏可执行意图。
- 先做
basename()→ 拆点 → 多段拦截 - 再调用
finfo_open(FILEINFO_MIME_TYPE)读取真实类型 - 最后映射白名单:比如
image/jpeg只允许存为.jpg,且强制覆盖用户传入的所有后缀 - 生成新文件名时,**完全丢弃**
$_FILES['file']['name'],用uniqid() . '.' . $safe_ext构造
Web 服务器配置才是最后一道保险
即使 PHP 层拦住了双后缀,如果上传目录的 Apache/Nginx 配置允许 .jpg 解析为 PHP,那所有校验都白做。
- Apache:确认没有
AddType application/x-httpd-php .jpg这类危险配置 - Nginx:检查
location ~ \.jpg$块里是否意外包含了fastcgi_pass - 上传目录务必禁用脚本执行:Apache 用
php_flag engine off,Nginx 用deny all或仅允许静态资源 - 最稳妥做法:上传目录放在 Web 根目录之外,比如
/var/data/uploads/,连 URL 访问路径都不存在
双后缀绕过不是“能不能检测”的问题,而是“是否允许用户控制任何一段后缀”的问题。只要还拼接、还信任、还保留原始文件名里的任意字符,就等于给攻击者留了缝。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











