当前位置:首页 > 焦点

目录遍历修复建议

目录遍历修复建议

去年帮一个做电商的目录朋友看服务器日志,发现有人用 ../../../../etc/passwd 这种路径在他们的遍历图片下载接口上反复试探。朋友当时还觉得没什么——"不就是修复读个系统文件吗,又没拿到数据库密码"。建议我让他把日志往前翻三天,目录结果发现攻击者已经用同样的遍历手法读到了 /proc/self/environ,里面赫然躺着数据库连接串和 Redis 密码。修复这就是建议目录遍历漏洞最危险的地方:单次请求看着无害,组合起来就是目录完整的入侵链。

修复这个问题的遍历核心思路只有一条:不要让用户输入直接参与文件路径的拼接。但实际落地时,修复不同场景下的建议处理方式差别很大,下面按我踩过的目录坑来分。

路径规范化要放在校验之前

很多开发者会写这样的遍历代码:先检查输入里有没有 ../,没有就拼接到基础目录后面。修复问题在于,攻击者可以用 URL 编码、双重编码、甚至 Unicode 变体绕过这种字符串匹配。更稳妥的做法是先做路径规范化,再判断最终路径是否在允许的目录内。

以 Java 为例,File.getCanonicalPath() 会解析掉所有 .. 和符号链接,拿到真实路径后再用 startsWith() 判断前缀。注意这里有个细节:startsWith() 要带上路径分隔符,否则 /data/files_evil 会被误判为在 /data/files 目录下。PHP 里对应的函数是 realpath(),Python 用 os.path.realpath(),思路一致。

能不用路径参数就别用

我见过最干净的修复方案,是把文件 ID 映射到实际路径。用户传 ?file_id=1024,后端查数据库拿到真实存储路径。这样攻击者连拼路径的机会都没有。代价是需要维护一张映射表,但对于文件数量可控的业务来说,这是最省心的做法。

如果业务上必须让用户传文件名,那就用白名单。比如只允许 [a-zA-Z0-9_-]+\.(jpg|png|gif) 这种格式,正则匹配通过后再拼接。白名单比黑名单可靠得多——你永远想不到攻击者能构造出什么奇怪的路径。

框架自带的防护别浪费

Spring 的 ResourceHttpRequestHandler 默认就会阻止路径穿越,但前提是你用它的静态资源映射,而不是自己写 new FileInputStream(userInput)。Django 的 FileResponse 配合 os.path.abspath() 也能挡住大部分攻击。问题是很多人为了"灵活"绕过了框架的安全机制,自己拼路径,结果把漏洞带进来了。

还有个容易忽略的点:Nginx 的 alias 指令如果配置不当,本身就会引入目录遍历。比如 location /static { alias /data/static/; } 这种写法,访问 /static../ 就可能穿越到上级目录。正确做法是用 root 替代 alias,或者严格限制 location 匹配规则。

权限最小化是最后一道墙

假设前面的防护都被绕过了,运行 Web 服务的系统用户权限应该尽可能低。别用 root 跑应用,文件目录的属主和权限也要收紧。我习惯把上传目录设成 www-data:www-data 且权限 750,这样即使攻击者读到了路径,也拿不到敏感文件的内容。

另外,日志里要记录完整的请求路径和规范化后的路径。出现 .. 或编码字符时触发告警。我朋友那次如果早几天看日志,也不至于被拖了三天才发现。目录遍历不是那种一锤子买卖的漏洞,它更像是在墙上凿洞——每次凿一点,最后整面墙都会塌。

分享到: