应弃用原始文件名,改用md5_file($file->getRealPath()).'.'.$file->guessExtension()生成唯一安全文件名,并按日期分层存储;原始名仅可经htmlspecialchars后存数据库。
ThinkPHP 获取上传文件原始文件名的正确方式
直接用
拿到的是客户端提交的原始文件名,但这个值不可信——浏览器可伪造、含路径、带编码、甚至含恶意字符。ThinkPHP 默认会做基础过滤(如去除路径),但不会解码或标准化,所以不能直接用于存储或展示。
是更安全的选择,它内部调用
尝试还原原始编码,并剥离路径,但依然不保证 100% 可读(比如 IE 上传的 GBK 文件名可能乱码)
若需强一致性,建议在前端用
编码文件名,后端用
+
手动处理
永远不要把原始文件名拼进 SQL 或直接输出到 HTML,否则可能触发 XSS 或路径遍历
ThinkPHP 文件上传时的 Security 过滤默认行为
ThinkPHP 6 的
类在实例化时会自动调用
方法,对原始文件名做两件事:一是用正则
剔除 Windows 路径与非法字符;二是用
截取最后一段。但它
不会
过滤空字节、Unicode 控制字符、或同形字(如全角冒号)。
默认不校验文件扩展名是否与 MIME 匹配,
可被伪造,必须配合
或
实际检测内容
上传目录权限必须设为非 Web 可执行(如不放
解析规则),且禁止解析用户上传的任意后缀
中的
是白名单校验,但仅检查后缀,不是最终防线
绕过 ThinkPHP 安全过滤的常见漏洞场景
攻击者常利用开发者忽略的边界情况绕过防护,比如上传
(后缀合法但实际是 PHP)、或
(双点+反斜杠绕过 basename)。ThinkPHP 的
对后者能拦截,但对前者无感。
Windows 下用
或
组合可能绕过部分旧版过滤逻辑(TP 6.0.13+ 已加固)
上传文件名含
(空字节)会导致
截断,后续字符串操作失效(PHP 7.4+ 已修复,但低版本仍存在)
使用
时若
来自用户输入(如表单传的 name 字段),完全跳过框架过滤,极其危险
生产环境推荐的文件名生成与存储方案
别依赖原始文件名做任何关键逻辑。真实项目中应彻底弃用它,改用服务端可控的命名策略。
PHP 8.5.5
PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。
下载
立即学习
“
PHP免费学习笔记(深入)
”;
文件名用
生成,确保唯一且可校验内容
存储路径按日期分层,如
,避免单目录海量文件
如果业务强制需要保留原始语义(如合同上传),可将原始名经
后存入数据库字段,而非文件系统
开启
和
限制,并在 Nginx/Apache 层加
,防大文件耗尽内存
最易被忽略的一点:ThinkPHP 的
规则只在校验阶段生效,一旦
开始执行,就不再检查。所以所有安全逻辑必须在 move 前完成,包括 MIME 检测、内容扫描、病毒查杀等。
$file->getInfo('name')$file->getOriginalName()mb_convert_encodingencodeURIComponenturldecodemb_convert_encodingFilefilterFilename/[/\:*?"\|]/basename()$file->getMime()getimagesize()finfo_open().phpvalidate(['size'=>1024*1024, 'ext'=>'jpg,png,gif'])extshell.php.jpgxxx..\..shell.phpfilterFilename..\../%00basename()$file->move($path, $name)$namemd5_file($file->getRealPath()) . '.' . $file->guessExtension()uploads/2024/06/15/xxx.jpghtmlspecialchars()upload_max_filesizepost_max_sizeclient_max_body_sizevalidatemove()