thinkphp 5.1+ 默认不拦截../,input()和request::param()返回原始字符串,必须用realpath()解析真实路径并严格校验是否以白名单根目录开头,仅str_replace或basename无法防御绕过。

ThinkPHP 5.1+ 默认不拦截 ../,input() 和 Request::param() 返回的是原始字符串——你直接拼进 file_get_contents()、unlink() 或 include,就等于把服务器文件系统交到攻击者手上。
realpath() + 前缀校验是唯一可靠防线
仅靠 str_replace(['..', './'], '', $input) 或正则删点号,100% 被绕过。真正有效的做法是让 PHP 解析出真实绝对路径,再确认它是否落在授权目录内:
-
$root = realpath('./public/uploads')必须用realpath()得到真实路径,且末尾带斜杠(如/var/www/html/public/uploads/),否则/uploads2/evil.txt可能被误判为合法 -
$full = realpath('./public/uploads/' . basename(urldecode($input))):顺序不能错——先urldecode()(防%2e%2e%2f绕过),再basename()(剥离路径结构),最后realpath()(解析符号链接、多重斜杠、空字节截断等) - 校验必须用
strpos($full, $root) === 0,不要用str_starts_with()——PHP 8.0+ 才支持,老环境会报错 -
realpath()在open_basedir限制外会返回false,这反而是好事,应统一拦截,别当成“文件不存在”处理
basename() 只是第一步,不是终点
很多人写 $file = basename(input('get.file')) 就以为安全了,其实它只做字符串截断,完全不管编码、空字节、符号链接或协议伪路径:
-
../../../etc/passwd%00.jpg经basename()变成passwd%00.jpg,后续若用pathinfo($file, PATHINFO_EXTENSION)判断类型,%00后内容会被忽略,扩展名校验失效 -
http://evil.com/shell.php经basename()仍得shell.php,但include()会直接远程加载 -
.//./../config.php这类多重斜杠 + 点号组合,basename()不解析也不清理,原样保留 - 所以
basename()后必须立即做白名单字符过滤:preg_match('/^[a-zA-Z0-9_.-]+$/', $safe_name)
上传/下载/删除场景必须禁用用户路径拼接
所有涉及文件操作的场景,都不该让用户控制完整路径结构:
- 上传文件一律强制重命名:
uniqid() . '_' . md5($original),绝不保留原始名直接拼路径 - 下载时不用
input('file')拼路径,改用 ID 映射:db('files')->where('id', input('id'))->value('real_path') - 删除前必须双重校验:
is_file($full)+is_writable($full),且$full已通过realpath()+ 前缀校验 - 多语言场景下,
Lang::setLocale(input('lang'))不能直接用,必须白名单过滤:in_array($lang, ['zh-cn', 'en-us']) ? $lang : 'zh-cn'
路由层也要防路径遍历式试探
ThinkPHP 的隐式路由机制(url_route_must = false)会让攻击者批量探测未公开路径,比如 /admin、/api/user:
- 必须在
config/route.php中设'url_route_must' => true,否则Route::miss()不生效 -
Route::miss()必须写成abort(404, 'Not Found'),不能重定向或渲染模板——那会给攻击者明确反馈 - 中间件里要监听
Request::pathinfo(),对含../、php://、data://或连续点号的路径直接abort(403) - 禁用
url_common(config/app.php中设'url_common' => false),防止?s=xxx形式绕过路由系统
最危险的不是没写校验,而是写了 basename() 或 str_replace() 就以为万事大吉——这些操作不解析路径语义,也挡不住编码、空字节、符号链接和 Unicode 归一化绕过。真正起作用的永远是 realpath() 解析后的绝对路径 + 严格前缀比对。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











