非预期目录遍历漏洞
一个正常的非预上传接口,用户传完文件后页面跳转到 `/uploads/2024/05/abc123.jpg`,期目一切看起来没问题。录遍历漏但如果把路径改成 `/uploads/../../etc/passwd`,非预服务器真把 `/etc/passwd` 的期目内容吐回来了——这就是非预期目录遍历漏洞。它不挑语言,录遍历漏PHP、非预Java、期目Python、录遍历漏Node.js 全中过招,非预核心原因只有一个:程序拿用户输入的期目路径去拼文件路径,却没管里面有没有 `../`。录遍历漏 去年我帮朋友看一个开源 CMS,非预后台有个"模板预览"功能,期目请求参数是录遍历漏 `&file=default.html`。我随手改成 `&file=../../../../etc/hosts`,页面直接渲染出了 hosts 文件内容。开发者的过滤逻辑是 `str_replace('../', '', $file)`,以为删掉就没事了。但 `....//` 经过一次替换后变成 `../`,照样逃逸。这种"删一次就以为干净"的写法,我在至少五个项目里见过。 目录遍历说白了就是路径拼接失控。程序想读 `/var/www/templates/` 下的文件,用户却用 `../` 一路退回根目录,再进入任意位置。操作系统本身不拦这个——`../` 是合法路径语法,文件系统只认最终解析结果。问题出在应用层没有把用户输入约束在预期目录内。 常见的错误过滤方式有好几种。除了上面说的单次替换,还有只检查开头是不是 `../`,结果用户把 `../` 放在中间就绕过了;或者只过滤小写,忘了 Windows 对大小写不敏感,`..\` 和 `../` 都能用;更离谱的是用黑名单拦 `../`,但没拦 URL 编码后的 `%2e%2e%2f`,解码后照样生效。这些坑我都踩过,也看别人踩过。 真正靠谱的做法不是过滤,而是白名单。用户请求的文件名,先做一次规范化,解析出绝对路径,然后判断这个路径是否在允许的根目录之下。Java 里可以用 `Path.normalize()` 加 `startsWith()`,Python 用 `os.path.realpath()` 再比对前缀。关键点是:先规范化,再比较,顺序不能反。规范化会把 `../` 和符号链接都展开,得到最终的真实路径,这时候再判断它有没有跑出边界。 还有一种情况容易被忽略:文件下载接口。用户传 `filename=report.pdf`,后端拼成 `/data/reports/report.pdf` 返回。如果把 filename 改成 `../../../etc/shadow`,只要权限够,服务器就把 shadow 文件发出来了。这种接口往往还带 Content-Disposition 头,攻击者能直接拿到文件内容,危害比读取更大。 我见过一个真实案例,某企业内网的文件服务器用 PHP 写的,下载参数直接拼路径。攻击者用 `../../../../../../proc/self/environ` 读到了环境变量,里面藏着数据库密码。从目录遍历到敏感信息泄露,只差一个没做路径校验的下载接口。这类漏洞在 CVE 里一抓一大把,很多都是因为开发者觉得"用户不会那么坏"。 防御上,除了路径规范化加白名单,还要注意权限最小化。Web 进程不该有读取 `/etc/shadow` 的权限,也不该能访问应用目录之外的地方。容器化部署时把文件系统挂载限制住,就算遍历成功也读不到关键文件。多层防御比单靠代码过滤可靠得多。 目录遍历漏洞不复杂,但极容易漏。每次看到文件操作相关的参数,我都会习惯性试一下 `../`。这个习惯帮我发现过三个线上漏洞,也让我在写代码时养成了先规范化再校验的条件反射。
-
上一篇
-
下一篇