ThinkPHP如何防范文件包含漏洞【安全】
ThinkPHP 在路径处理上其实一直有个“默认不设防”的特点——input() 和 Request::param() 拿到的路径类参数(比如 file、view、template)都是原始字符串,不做任何过滤。如果你直接把这些字符串拼进 include、file_get_contents() 或
ThinkPHP 在路径处理上其实一直有个“默认不设防”的特点——input() 和 Request::param() 拿到的路径类参数(比如 file、view、template)都是原始字符串,不做任何过滤。如果你直接把这些字符串拼进 include、file_get_contents() 或 think\facade\View::fetch(),那就等于把服务器文件系统的访问权限拱手让给了攻击者。
那么问题来了:想防住路径遍历,到底该怎么操作?先看一个常见的误区。
为什么 basename() 单独用不安全
basename() 这个函数看起来很方便——它只取路径的最后一部分。但麻烦在于,它只做字符串截断,完全不关心编码、空字节、符号链接或协议伪路径。举个例子:
../../../etc/passwd%00.jpg经过basename()变成passwd%00.jpg。如果你用file_exists()判断存在再拼路径,旧版 PHP 中%00会截断字符串,校验直接失效。http://evil.com/shell.php经过basename()仍然得到shell.php,但include()会直接远程加载它。.//./../config.php这种多重斜杠+点号的组合,basename()既不解析也不清理,原样保留。
所以,basename() 只能作为第一步——强制剥离路径结构,之后必须配合白名单字符过滤和 realpath() 校验才能生效。
realpath() + strpos() 是唯一可靠路径校验组合
有些开发者尝试用 str_replace(['..', './'], '', $input) 或正则表达式来删除点号,但这类做法 100% 会被绕过。真正可靠的办法是让 PHP 自己解析出真实绝对路径,然后确认它是否落在授权目录内。具体步骤很清晰:
$root = realpath('./app/views');— 必须先通过realpath()得到真实路径,并且末尾带上斜杠。$requested = realpath('./app/views/' . basename(urldecode($input)));— 顺序不能乱:先urldecode()解码,再basename()取文件名,最后realpath()得到绝对路径。if (false === $requested || 0 !== strpos($requested, $root . '/')) { abort(403, 'Invalid file path'); }— 如果realpath()返回 false(路径不存在)或结果不在授权目录下,直接终止请求。
这里有几个关键点需要注意:
$root末尾必须加斜杠,否则像/app/views2/xxx这样的路径会被误判为合法。realpath()对于不存在的路径返回false,不能简单地当成“文件不存在”放过——必须统一拦截,因为可能是恶意构造的路径。- 别用
str_starts_with(),PHP 8.0+ 才支持,老环境直接报错。用strpos()或substr()更稳妥。 - 如果用了
View::fetch()并传入用户参数,必须先走这套校验,不能直接透传。
上传和下载场景必须切断路径拼接
上传时 $_FILES['file']['name'] 和下载接口的 filename 参数,是路径遍历攻击的高发区。永远不要直接把它们拼进路径里:
- 不要写
file_get_contents('./uploads/' . $_GET['filename']),这种代码一写就炸。 - 不要用
header('Content-Disposition: attachment; filename="' . $_GET['filename'] . '"')直接输出用户输入,因为文件名里可能包含路径分隔符。 - 下载接口应该只允许预设的 ID(如
download?id=123),通过查数据库映射到内部安全路径。 - 上传的文件名必须重命名(如 UUID + 时间戳),原始的
name字段仅作展示,绝不能参与路径构造。
说到底,写对几行校验代码并不难,真正难的是确保所有路径类入口——模板渲染、多语言加载、插件配置读取、日志导出——都经过同一套校验逻辑。漏掉一个 Lang::load() 或 Config::load() 的调用点,就等于在防火墙上凿了个洞。



































