事情本身不复杂,线索都在日志里。文件上传接口只判断文件名是否以 .jpg 结尾,安全测试把 webshell 改名成 shell.jpg.php 后上传成功,落在可执行目录下能被直接访问,接口返回的还是上传成功和访问链接。

第一反应是「文件名直接拼接存储路径,没有重命名」,验证下来不成立。

把日志和配置对完,能解释这个现象的基本就这几处。只校验扩展名,没有校验内容类型与文件头。也可能是上传目录配置了脚本执行权限。

不容易想到的是,黑名单只拦了 .php 一种,漏掉 .phtml 与 .php5。

先看服务端拿到的 content-type 与文件头

curl -s -X POST http://127.0.0.1:8000/upload -F 'file=@shell.jpg.php;type=image/jpeg' -o - -w '%{http_code}\n'

随后用 file 命令确认实际类型

file --mime-type shell.jpg.php

服务端改成按文件头判定并重命名

python -c "import imghdr;print(imghdr.what('shell.jpg.php'))"

去掉上传目录的执行权限

chmod -R 644 /data/upload; find /data/upload -type d -exec chmod 755 {} \;

改完先别撤,确认这几项。改名的脚本文件被拒并返回明确错误,正常图片上传不受影响,上传目录里的文件只能被读取不能被解析执行。

提醒一点。扩展名是客户端说了算的,服务端要按文件头判类型、重命名落盘、目录禁执行,这三步缺一步都能被绕过。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189