文章总结: 本文复盘一次路径遍历漏洞攻防实战,通过下载接口仅改几个字符即读取系统口令文件与应用源码。漏洞成因为用户输入直接拼接文件路径,过滤黑名单可被编码绕过、双重编码、空字节截断、绝对路径、平台差异等手法绕过。修复方案为白名单校验、路径规范化后判断、编号映射文件名、牢笼机制等七条措施,并提供自查清单。
综合评分: 85
文章分类: 漏洞分析,WEB安全,渗透测试,代码审计,安全开发
路径遍历漏洞:一个下载接口引发的系统文件泄露
原创
钟智强
钟智强
哪吒网络安全
2026年9月30日 01:38
马来西亚
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
只改了几个字符,服务器就把整个系统文件交了出来
路径遍历 | 目录穿越 | 白名单校验 | 攻防复盘
一、一个再普通不过的下载功能
那天我在看一个医疗平台的下载接口。功能很平常:页面上放一个链接,点一下,取回一份文件。这类接口在任何业务系统里都无处不在——下载报告、下载附件、下载图片。
我做的动作也很小:把链接里那个文件名,换成了一串“往上退”的符号。然后服务器回了我一个两百(请求成功)。接着,它把系统里最不该给人看的那份系统口令文件,一行一行、原封不动地打了出来。
整个过程,我没有拿到任何账号,没有绕过任何登录,没有碰任何密码。我只是改了地址栏里的几个字符。
二、这个漏洞到底是怎么发生的
它有个正式的名字,叫路径遍历,也有人叫它目录穿越、点点斜杠、向上回溯。道理其实特别朴素。
一个正常的下载链接长这样:服务器把你要的文件名,拼在一个固定的目录后面,然后去硬盘上取。比如目录是“存放文件的文件夹”,你要的是“报告文件”,它就变成“存放文件的文件夹 / 报告文件”。
问题出在哪?那个文件名,是你传的。
于是你传的不是文件名,而是“往上退一层、再往上退一层……退到根目录,然后取系统口令文件”。服务器老老实实地照做了。它没有意识到自己被骗,它只是觉得你要的文件路径有点长。
图 一 | 路径遍历攻击链:四个环节,缺一不可
用开放网页应用安全项目的原话说就是:通过操纵引用文件的变量,访问存放在网页根目录之外的文件与目录,包括应用源代码、配置文件和系统关键文件。
一句话总结:你让服务器去拿东西,却没告诉它只能在哪个柜子里拿。
图 二 | 正常取文件 与 恶意穿越 的路径对比
三、实战复现:两张截图说清整个过程
下面两张图,是同一次排查里的真实请求记录。目标信息已做脱敏处理。
截图一:读到了系统口令文件
图 三 | 下载参数被改成“连续往上退四层,再取系统口令文件”。服务器返回两百(请求成功),并把系统口令文件的正文直接回显在响应里。注意响应头里那串重复出现的会话凭证字段——这同样是从这一个请求里带出来的。
这张图说明的第一件事:穿越真实生效了,不是理论。
那串重复的会话凭证字段,本身也是个信号:响应在处理“删除用户凭证”这类动作,说明这条请求链路后端还挂着别的状态逻辑,值得单独再看一眼。
截图二:读到了应用自己的文件
图 四 | 同一个下载参数,这次指向站点自身目录下的某个脚本页面。服务器同样返回两百(请求成功),内容类型显示为网页文本,页面标题是一张“联系我们”页——说明这次穿越读到的,是应用自己的文件。
这张图说明的第二件事,比第一张更严重:
能读系统文件,意味着拿到了服务器;能读应用文件,意味着拿到了源码。
源码里有数据库连接方式、有接口地址、有内部逻辑、有写死的密钥。攻击者拿到源码之后,后面的每一步都会变得容易得多。
四、为什么“过滤一下”根本没用
很多人第一反应是:那我过滤掉“往上退”的那几个字符不就行了?不行。原因是攻击者的花样远比过滤名单多。
实际请求里见过的变形写法
| |
| — |
| |
| 【同一意图的多种变形写法 · 中文示意】 原形 :连续向上回退四层,再取系统口令文件 变形一 :把回退符号写成「百分号 + 数字」的形式 变形二 :只编码其中一部分,其余保持原样 变形三 :在编码之上再编码一层(服务端默认只解一层) 变形四 :改用另一种系统也认的分隔符写法 变形五 :干脆不用回退,直接报完整的绝对路径 变形六 :文件名后塞一个空字节,再补一个合法后缀 说明:上述六种写法,服务端解析后指向的是同一个目标。 过滤名单只能挡住它认识的那一种,挡不住其余五种。 |
代码一 | 同一意图的多种写法:过滤名单永远追不上变形速度
五种绕过手法一览
| | | |
| — | — | — |
| 手法 | 做法 | 为什么过滤失效 |
| 编码绕过 | 把回退符号写成百分号加数字的形式 | 过滤比对的是编码后的字符串,看不出危险 |
| 双重编码 | 在编码之上再编码一层 | 服务端默认只解一层,过滤与系统看到的不是同一串 |
| 空字节截断 | 文件名后塞空字节再补合法后缀 | 应用校验的是后半段,系统打开的是前半段 |
| 绝对路径 | 直接传完整路径,一步到位 | 过滤只盯着回退符号,这串压根不含回退符号 |
| 平台差异 | 换用另一种系统认的分隔符写法 | 只防了一种系统写法,另一种就漏了 |
表 一 | 五种常见绕过手法与失效原因
所以开放网页应用安全项目给的建议非常明确:只接受已知良好的输入,做白名单,不要试图清洗坏数据。黑名单永远追不上想象力,白名单才是终点。
五、正确的修法
先看一段教科书级的脆弱写法
| |
| — |
| |
| 【危险写法示意】 第一步 固定目录 = 「存放文件的文件夹」(代码写死) 第二步 文件名 = 用户传来的内容(完全可控) 第三步 最终路径 = 固定目录 + 文件名 第四步 直接读取(最终路径) 问题所在:第三步把一段完全由用户控制的内容, 拼进了一个本该封闭的路径里。 |
代码二 | 目录由代码写死,但文件名那段完全交给了用户
改成这样就安全了
| |
| — |
| |
| 【正确写法示意】 写法一:用编号代替真实文件名 第一步 允许清单 = 【文件一,文件二,文件三】 第二步 用户提交 = 一个编号 第三步 取出的文件 = 允许清单 中的第 编号 项 第四步 最终路径 = 固定目录 + 取出的文件 用户从头到尾碰不到真实路径。 写法二:必须用用户输入时,先规范化、后判断 第一步 基准根目录 = 规范化(允许访问的根目录) 第二步 完整路径 = 规范化(基准根目录 + 用户输入) 第三步 判断:完整路径 是否仍以 基准根目录 开头 不是 → 拒绝访问 是 → 允许读取 关键:顺序不能颠倒。先过滤再规范化,等于没防。 |
代码三 | 白名单取值 + 规范化后判断,两条路都能堵死穿越
图 五 | 正确的防护顺序:先规范化、后判断
七条完整修法清单
| | | |
| — | — | — |
| 序号 | 修法 | 要点 |
| 一 | 文件系统调用不使用用户输入 | 用编号代替文件名,用户提交编号,代码映射成真实文件 |
| 二 | 路径由代码包住 | 用户最多只能提供文件名的一部分,目录那段写死在代码里 |
| 三 | 校验用白名单 | 先列允许清单,不在清单里一律拒绝,而不是去找危险字符 |
| 四 | 读取前先规范化 | 先规范化路径,再判断是否越界;先过滤再规范化等于没防 |
| 五 | 上牢笼机制 | 用根目录隔离与代码访问策略,把可存取范围框死 |
| 六 | 部署上做减法 | 敏感配置不放网页根目录;视窗系统网页根目录不放系统盘 |
| 七 | 关掉错误回显 | 报错把路径与堆栈打出来,等于免费送攻击者一张地图 |
表 二 | 七条修法,按优先级排列
六、自查清单:三句话
·我的代码里,有没有任何一个地方,把用户传来的内容拼进了文件路径?
·如果有,我用的是“禁止什么”,还是“只允许什么”?
·如果只允许,我是在拼好路径之后判断的,还是在规范化之后判断的?
三句话里只要有一句答不上来,就值得现在去查一遍。
七、写在最后
这次排查里,最让我印象深刻的不是漏洞本身有多高明,而是它有多普通。没有复杂的工具,没有高超的技巧,只是几个字符。
而它背后的成因,同样普通到让人无奈:一个再常见不过的功能,一次想当然的“用户不会乱填”。
真正的安全问题,往往不是被高明的攻击打穿的,是被“应该没事吧”这四个字打穿的。
合规提醒
| |
| — |
| |
| 文中目标信息已全部脱敏,截图不含可直接利用的细节。漏洞测试应在明确授权范围内进行,发现的问题请通过正规渠道及时报送责任方修复,切勿在公开渠道披露可被直接利用的细节。同一次排查中发现的其他类型隐患(例如登录入口相关的注入类问题),本文不作展开,已另行整理上报;涉及管理员凭据的内容,尤其不应出现在任何公开材料中。 |
加入我们团队。以下是群聊二维码:
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:哪吒网络安全 钟智强
钟智强《路径遍历漏洞:一个下载接口引发的系统文件泄露》