thinkphp文件上传漏洞源于校验链断裂、存储路径失控及服务端配置松懈;须多重验证扩展名、mime与文件头,强制重命名并存至非web可访问目录,禁用原始文件名与脚本解析权限。

ThinkPHP文件上传漏洞不是单一环节失守,而是校验链断裂、存储路径失控、服务端配置松懈共同导致的结果。光靠前端限制或只验后缀,基本等于给攻击者发邀请函。
后缀校验必须做,但不能只做后缀校验
ThinkPHP的ext规则(如['ext' => 'jpg,png,gif'])只检查文件名后缀,不读取文件内容。攻击者把shell.php改成shell.jpg就能绕过。这个校验发生在think\File::validate()中,需显式调用,不是自动触发。别把它硬编码在控制器里,应统一收口到策略类或配置项中,方便后续批量调整。
- 禁用用户原始文件名,强制重命名为
uniqid() . '.' . $safeExt - 拒绝带点的文件名(如
shell.php.jpg)、双扩展名、空格和特殊字符 - 禁止使用
pathinfo($name, PATHINFO_EXTENSION)提取后缀,改用正则匹配末尾真实扩展
MIME与文件头必须双重验证
$_FILES['file']['type']完全由浏览器提交,可随意伪造,ThinkPHP默认不校验它。更危险的是,框架也不解析文件头(magic bytes)。一个PNG文件,开头写\x89PNG\r\n\x1a\n就可信;一个GIF,必须是GIF87a或GIF89a。PHP的finfo_open(FILEINFO_MIME_TYPE)能更可靠推断真实类型,但需确认fileinfo扩展已启用,否则会退化为不可靠的字符串匹配。
- 不要用
getimagesize()代替文件头检测——它对非图片返回false,且不防伪装 - 对图片类文件,额外校验宽高是否为合法正整数,排除纯文本伪装
- 非图片类文件(如PDF、DOCX)也需读取前几百字节,比对标准文件头特征
存储路径必须隔离,临时文件不能留坑
ThinkPHP默认把上传文件暂存到runtime/upload/,如果该目录被Web服务器映射为可访问路径,或配置了错误的解析规则(比如Apache把.png当PHP执行),上传的恶意文件就直接可执行。目标路径绝不能落在public/或webroot/下,推荐存到storage/app/uploads/这类非Web可访问目录。
-
moveTo()前务必检查目标路径是否在Web根目录之外,可用realpath()比对 - 跨磁盘分区时
moveTo()会失败,应改用copy() + unlink() - 临时文件不会因脚本异常退出而自动清理,建议上传成功后立即
unlink()原临时路径
别忽略服务器层和框架配置风险
ThinkPHP本身不控制Web服务器行为,但攻击常借力打力。例如,Nginx未关闭autoindex,可能暴露上传目录结构;Apache开启MultiViews或错误配置AddHandler,会让shell.jpg.php被当作PHP执行。另外,ThinkPHP多语言模块若开启且未过滤语言包名,也可能被用于远程文件包含(QVD-2022-46174)。
- 关闭Web服务器对上传目录的脚本解析权限,如Nginx中加
location ~ \.(php|phtml|php3|php4|php5|php7|php8|sh|bash|pl|py|jsp|asp|aspx|cgi|exe|dll|so|bin)$ { deny all; } - 禁用ThinkPHP多语言模块的远程加载能力,设置
lang_switch_on = false或严格白名单过滤语言包名 - 上传目录设为
chmod 755,文件设为644,禁止执行位
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











