夜雨聆风学习资料网

ARTICLE · 1101212

路径遍历漏洞:一个下载接口引发的系统文件泄露

路径遍历漏洞:一个下载接口引发的系统文件泄露

只改了几个字符,服务器就把整个系统文件交了出来

路径遍历 | 目录穿越 | 白名单校验 | 攻防复盘

一、一个再普通不过的下载功能

那天我在看一个医疗平台的下载接口。功能很平常:页面上放一个链接,点一下,取回一份文件。这类接口在任何业务系统里都无处不在——下载报告、下载附件、下载图片。

我做的动作也很小:把链接里那个文件名,换成了一串“往上退”的符号。然后服务器回了我一个两百(请求成功)。接着,它把系统里最不该给人看的那份系统口令文件,一行一行、原封不动地打了出来。

整个过程,我没有拿到任何账号,没有绕过任何登录,没有碰任何密码。我只是改了地址栏里的几个字符。

二、这个漏洞到底是怎么发生的

它有个正式的名字,叫路径遍历,也有人叫它目录穿越、点点斜杠、向上回溯。道理其实特别朴素。

一个正常的下载链接长这样:服务器把你要的文件名,拼在一个固定的目录后面,然后去硬盘上取。比如目录是“存放文件的文件夹”,你要的是“报告文件”,它就变成“存放文件的文件夹 / 报告文件”。

问题出在哪?那个文件名,是你传的。

于是你传的不是文件名,而是“往上退一层、再往上退一层……退到根目录,然后取系统口令文件”。服务器老老实实地照做了。它没有意识到自己被骗,它只是觉得你要的文件路径有点长。

图 一 | 路径遍历攻击链:四个环节,缺一不可

用开放网页应用安全项目的原话说就是:通过操纵引用文件的变量,访问存放在网页根目录之外的文件与目录,包括应用源代码、配置文件和系统关键文件。

一句话总结:你让服务器去拿东西,却没告诉它只能在哪个柜子里拿。

图 二 | 正常取文件 与 恶意穿越 的路径对比

三、实战复现:两张截图说清整个过程

下面两张图,是同一次排查里的真实请求记录。目标信息已做脱敏处理。

截图一:读到了系统口令文件

图 三 | 下载参数被改成“连续往上退四层,再取系统口令文件”。服务器返回两百(请求成功),并把系统口令文件的正文直接回显在响应里。注意响应头里那串重复出现的会话凭证字段——这同样是从这一个请求里带出来的。

这张图说明的第一件事:穿越真实生效了,不是理论。

那串重复的会话凭证字段,本身也是个信号:响应在处理“删除用户凭证”这类动作,说明这条请求链路后端还挂着别的状态逻辑,值得单独再看一眼。

截图二:读到了应用自己的文件

图 四 | 同一个下载参数,这次指向站点自身目录下的某个脚本页面。服务器同样返回两百(请求成功),内容类型显示为网页文本,页面标题是一张“联系我们”页——说明这次穿越读到的,是应用自己的文件。

这张图说明的第二件事,比第一张更严重:

能读系统文件,意味着拿到了服务器;能读应用文件,意味着拿到了源码。

源码里有数据库连接方式、有接口地址、有内部逻辑、有写死的密钥。攻击者拿到源码之后,后面的每一步都会变得容易得多。

四、为什么“过滤一下”根本没用

很多人第一反应是:那我过滤掉“往上退”的那几个字符不就行了?不行。原因是攻击者的花样远比过滤名单多。

实际请求里见过的变形写法

【同一意图的多种变形写法 · 中文示意】

原形  :连续向上回退四层,再取系统口令文件

变形一 :把回退符号写成「百分号 + 数字」的形式

变形二 :只编码其中一部分,其余保持原样

变形三 :在编码之上再编码一层(服务端默认只解一层)

变形四 :改用另一种系统也认的分隔符写法

变形五 :干脆不用回退,直接报完整的绝对路径

变形六 :文件名后塞一个空字节,再补一个合法后缀

说明:上述六种写法,服务端解析后指向的是同一个目标。

过滤名单只能挡住它认识的那一种,挡不住其余五种。

代码一 | 同一意图的多种写法:过滤名单永远追不上变形速度

五种绕过手法一览

手法

做法

为什么过滤失效

编码绕过

把回退符号写成百分号加数字的形式

过滤比对的是编码后的字符串,看不出危险

双重编码

在编码之上再编码一层

服务端默认只解一层,过滤与系统看到的不是同一串

空字节截断

文件名后塞空字节再补合法后缀

应用校验的是后半段,系统打开的是前半段

绝对路径

直接传完整路径,一步到位

过滤只盯着回退符号,这串压根不含回退符号

平台差异

换用另一种系统认的分隔符写法

只防了一种系统写法,另一种就漏了

表 一 | 五种常见绕过手法与失效原因

所以开放网页应用安全项目给的建议非常明确:只接受已知良好的输入,做白名单,不要试图清洗坏数据。黑名单永远追不上想象力,白名单才是终点。

五、正确的修法

先看一段教科书级的脆弱写法

【危险写法示意】

第一步 固定目录 = 「存放文件的文件夹」(代码写死)

第二步 文件名  = 用户传来的内容(完全可控)

第三步 最终路径 = 固定目录 + 文件名

第四步 直接读取(最终路径)

问题所在:第三步把一段完全由用户控制的内容,

     拼进了一个本该封闭的路径里。

代码二 | 目录由代码写死,但文件名那段完全交给了用户

改成这样就安全了

【正确写法示意】

写法一:用编号代替真实文件名

第一步 允许清单 = 【文件一,文件二,文件三】

第二步 用户提交 = 一个编号

第三步 取出的文件 = 允许清单 中的第 编号 项

第四步 最终路径 = 固定目录 + 取出的文件

    用户从头到尾碰不到真实路径。

写法二:必须用用户输入时,先规范化、后判断

第一步 基准根目录 = 规范化(允许访问的根目录)

第二步 完整路径  = 规范化(基准根目录 + 用户输入)

第三步 判断:完整路径 是否仍以 基准根目录 开头

    不是 → 拒绝访问

    是  → 允许读取

关键:顺序不能颠倒。先过滤再规范化,等于没防。

代码三 | 白名单取值 + 规范化后判断,两条路都能堵死穿越

图 五 | 正确的防护顺序:先规范化、后判断

七条完整修法清单

序号

修法

要点

一

文件系统调用不使用用户输入

用编号代替文件名,用户提交编号,代码映射成真实文件

二

路径由代码包住

用户最多只能提供文件名的一部分,目录那段写死在代码里

三

校验用白名单

先列允许清单,不在清单里一律拒绝,而不是去找危险字符

四

读取前先规范化

先规范化路径,再判断是否越界;先过滤再规范化等于没防

五

上牢笼机制

用根目录隔离与代码访问策略,把可存取范围框死

六

部署上做减法

敏感配置不放网页根目录;视窗系统网页根目录不放系统盘

七

关掉错误回显

报错把路径与堆栈打出来,等于免费送攻击者一张地图

表 二 | 七条修法,按优先级排列

六、自查清单:三句话

·我的代码里,有没有任何一个地方,把用户传来的内容拼进了文件路径?

·如果有,我用的是“禁止什么”,还是“只允许什么”?

·如果只允许,我是在拼好路径之后判断的,还是在规范化之后判断的?

三句话里只要有一句答不上来,就值得现在去查一遍。

七、写在最后

这次排查里,最让我印象深刻的不是漏洞本身有多高明,而是它有多普通。没有复杂的工具,没有高超的技巧,只是几个字符。

而它背后的成因,同样普通到让人无奈:一个再常见不过的功能,一次想当然的“用户不会乱填”。

真正的安全问题,往往不是被高明的攻击打穿的,是被“应该没事吧”这四个字打穿的。

合规提醒

文中目标信息已全部脱敏,截图不含可直接利用的细节。漏洞测试应在明确授权范围内进行,发现的问题请通过正规渠道及时报送责任方修复,切勿在公开渠道披露可被直接利用的细节。同一次排查中发现的其他类型隐患(例如登录入口相关的注入类问题),本文不作展开,已另行整理上报;涉及管理员凭据的内容,尤其不应出现在任何公开材料中。

加入我们团队。以下是群聊二维码:

相关学习资料