
本文实验均在个人本地部署、合法授权的 OWASP Juice Shop 靶场环境中完成仅用于安全学习、漏洞分析与防御研究。文件访问漏洞敏感文件暴露与路径限制绕过一 漏洞发现与验证1. 确认文件访问入口首先在浏览器中正常访问 OWASP Juice Shop观察页面中是否存在文件下载、静态资源或目录访问入口。在完成一次正常下单后进入历史订单页面可以看到系统提供了打印订单信息的功能。点击打印订单后浏览器会打开一个新的 PDF 页面。观察该 PDF 文件的 URL可以发现订单文件位于/ftp/路径下。http://localhost:3000/ftp/order_xxx.pdf这说明/ftp/不是随机猜测得到的路径而是 Juice Shop 业务中真实存在的文件资源目录。随后将 URL 中具体的订单文件名去除仅保留/ftp/路径http://localhost:3000/ftp/访问后发现页面返回了/ftp/目录下的文件列表。从目录列表可以看到该目录下不仅存在订单 PDF 文件还存在.md、.bak、.yml、.kdbx等多种类型的文件。因此可以初步判断当前应用存在一个可被浏览器直接访问的文件目录后续需要进一步分析其中是否包含不应公开的敏感文件。2. 正常文件访问基线在进行敏感文件验证前先选择一个正常公开文件作为访问基线。这里选择/ftp/legal.md作为测试对象。该文件从名称和内容上看属于 Legal Information / Terms of Use 类型文件具备一定的公开访问合理性。请求如下GET /ftp/legal.md HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 Referer: http://localhost:3000/ftp/ Cookie: languagezh_CN; welcomebanner_statusdismiss; cookieconsent_statusdismiss; tokenJWT_TOKEN服务端返回HTTP/1.1 200 OK Content-Type: text/markdown; charsetUTF-8 Content-Length: 3047响应正文中可以看到文件内容# Legal Information ... ## Terms of Use ...服务端返回状态码为200 OK并且Content-Type为text/markdown; charsetUTF-8这说明/ftp/legal.md可以被正常访问服务端会直接将该 Markdown 文件内容返回给客户端。3. 敏感文件访问验证在/ftp/目录中继续观察可以发现存在一个名为acquisitions.md的文件。从文件名来看acquisitions表示“收购”该文件可能涉及公司内部业务信息。与legal.md这类公开法律说明文件不同该文件更可能属于不应公开的内部文档。尝试访问目标文件GET /ftp/acquisitions.md HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 Referer: http://localhost:3000/ftp/ Cookie: languagezh_CN; welcomebanner_statusdismiss; cookieconsent_statusdismiss; tokenJWT_TOKEN服务端返回HTTP/1.1 200 OK Content-Type: text/markdown; charsetUTF-8 Content-Length: 909响应正文如下# Planned Acquisitions This document is confidential! Do not distribute! Our company plans to acquire several competitors within the next year. This will have a significant stock market impact as we will elaborate in detail in the following paragraph: ...从返回内容可以看到该文件标题为Planned Acquisitions并且正文中明确出现This document is confidential! Do not distribute!这说明该文件并不是普通公开说明文档而是带有“机密、禁止分发”语义的内部文档。与前面的legal.md相比文件访问结果文件性质/ftp/legal.md200 OK法律信息 / 使用条款具备公开访问合理性/ftp/acquisitions.md200 OK收购计划文档内容明确标注 confidential因此acquisitions.md可以作为敏感文件暴露的验证对象。该问题的关键不只是“访问到了一个文件”而是服务端将不应公开的敏感文档放在了 Web 可直接访问目录下。 用户只要发现 /ftp/ 目录或知道文件路径就可以直接访问该文件内容。因此该现象可以归纳为敏感文件暴露 文件资源访问控制不足4. 路径 / 后缀限制验证继续访问/ftp/目录中的其他文件时可以发现服务端对部分文件类型进行了限制。在目录列表中可以看到package.json.bak文件。从文件名来看该文件属于开发过程中的备份文件可能包含项目依赖、版本信息或开发环境相关信息正常情况下不应通过 Web 目录直接访问。尝试直接访问该文件GET /ftp/package.json.bak HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 Referer: http://localhost:3000/ftp/ Cookie: languagezh_CN; welcomebanner_statusdismiss; cookieconsent_statusdismiss; tokenJWT_TOKEN服务端返回HTTP/1.1 403 Forbidden Content-Type: text/html; charsetutf-8 Content-Length: 1934响应页面中可以看到错误信息Error: Only .md and .pdf files are allowed!同时错误堆栈中出现了与文件访问校验相关的路径at verify (/juice-shop/build/routes/fileServer.js:69:18) at /juice-shop/build/routes/fileServer.js:53:13从响应结果可以看出服务端对/ftp/目录下的文件访问设置了后缀限制。也就是说服务端允许访问.md和.pdf类型文件例如前面的/ftp/legal.md /ftp/acquisitions.md但当访问.bak备份文件时请求被服务端拒绝/ftp/package.json.bak → 403 Forbidden这一现象说明服务端并不是无条件返回/ftp/目录下的所有文件而是对文件后缀进行了检查。但是这种检查是否足够可靠还需要进一步验证。5. 路径限制绕过验证在上一步中直接访问.bak备份文件时服务端返回403 Forbidden并提示Only .md and .pdf files are allowed!这说明服务端对/ftp/目录下的文件访问进行了后缀限制只允许访问.md和.pdf文件。原始受限请求如下GET /ftp/package.json.bak HTTP/1.1 Host: localhost:3000服务端返回HTTP/1.1 403 Forbidden Content-Type: text/html; charsetutf-8错误信息为Error: Only .md and .pdf files are allowed!随后尝试构造带有特殊编码和允许后缀的请求GET /ftp/package.json.bak%2500.md HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8 Referer: http://localhost:3000/ftp/ Cookie: languagezh_CN; welcomebanner_statusdismiss; cookieconsent_statusdismiss; tokenJWT_TOKEN服务端返回HTTP/1.1 200 OK Content-Type: application/octet-stream Content-Length: 4263响应正文中可以看到package.json.bak的实际内容{name:juice-shop,version:6.2.0-SNAPSHOT,description:An intentionally insecure JavaScript Web Application,dependencies:{express:~4.16,express-jwt:0.1.3,jsonwebtoken:~8,sequelize:~4,sqlite3:~3.1.13}}这说明原本被限制访问的.bak备份文件通过特殊编码和允许后缀组合后被成功读取。两组请求对比如下测试项请求路径响应结果说明原始请求/ftp/package.json.bak403 Forbidden直接访问.bak文件被拒绝修改后请求/ftp/package.json.bak%2500.md200 OK成功读取原.bak文件内容其中package.json.bak 表示原始目标文件 %2500 表示经过编码处理的空字节形式 .md 表示追加的允许后缀该构造并不是简单地把.bak改成.md。如果直接访问/ftp/package.json.md服务端上并不存在这个文件因此无法读取原始备份内容。本次绕过的关键在于服务端在后缀校验阶段看到的是一个以 .md 结尾的路径 但在实际文件解析或读取阶段路径被重新解释 最终读取到了原本不允许访问的 package.json.bak 文件。因此该问题可以归纳为路径 / 后缀限制绕过 文件访问校验逻辑不一致从安全影响来看package.json.bak这类文件虽然不是用户隐私数据但它会暴露项目名称、版本、依赖库、脚本命令、测试框架等开发信息。在真实业务系统中如果类似备份文件被公开访问攻击者可以根据依赖库版本、框架信息和项目结构进一步分析潜在攻击面。因此文件访问安全不能只依赖简单的后缀判断而应在服务端对最终解析后的真实路径进行统一校验并避免将备份文件放在 Web 可直接访问目录下。二 原理分析1. 本案例中的文件访问流程从前面的实验可以看出Juice Shop 中存在一个可直接访问的/ftp/文件目录。本次实验中的访问流程可以概括为发现 /ftp/ 文件目录 ↓ 访问正常公开文件 legal.md ↓ 访问敏感文件 acquisitions.md ↓ 访问受限制文件 package.json.bak ↓ 通过特殊编码绕过后缀限制其中legal.md返回200 OK说明/ftp/目录下的部分文件允许被浏览器直接访问。acquisitions.md同样返回200 OK但文件内容中明确出现This document is confidential! Do not distribute!这说明该文件并不属于普通公开文件而是一个不应被直接暴露的敏感文档。因此第一类问题可以归纳为敏感文件被放在 Web 可直接访问目录下。2. 敏感文件暴露原因在第 3 步中/ftp/acquisitions.md可以被直接访问并返回完整文件内容。该问题的关键不在于请求过程有多复杂而在于文件本身已经位于可被浏览器访问的 /ftp/ 目录中。与/ftp/legal.md相比文件访问结果文件性质/ftp/legal.md200 OK公开法律信息 / 使用条款/ftp/acquisitions.md200 OK标注 confidential 的内部文档因此acquisitions.md的问题可以理解为服务端没有区分公开文件和敏感文件 导致敏感文件也可以像普通公开文件一样被访问。这类问题属于敏感文件暴露也可以看作文件资源访问控制不足。3. 后缀限制与绕过原因在第 4 步中直接访问/ftp/package.json.bak服务端返回403 Forbidden Only .md and .pdf files are allowed!这说明服务端对/ftp/目录下的文件访问做了后缀限制只允许访问.md和.pdf文件。但是在第 5 步中访问/ftp/package.json.bak%2500.md服务端返回200 OK并且响应内容是package.json.bak的实际内容。两次请求的对比如下请求路径响应结果说明/ftp/package.json.bak403 Forbidden直接访问.bak文件被拒绝/ftp/package.json.bak%2500.md200 OK成功读取原.bak文件内容这说明服务端的后缀限制并不可靠。本次绕过的核心原因可以概括为服务端后缀校验时认为请求路径符合规则 但实际读取文件时却读取到了原本被限制的 package.json.bak。也就是说校验逻辑和实际文件读取逻辑不一致。4. 本案例中的两个问题结合第一部分实验现象本案例主要体现了两个问题。第一个问题是敏感文件暴露/ftp/acquisitions.md 可以直接访问 但文件内容明确标注 confidential。第二个问题是路径 / 后缀限制绕过/ftp/package.json.bak 直接访问被拒绝 但 /ftp/package.json.bak%2500.md 可以成功读取文件内容。因此本案例可以总结为敏感文件暴露 后缀限制绕过其中敏感文件暴露说明/ftp/目录中的文件缺少合理的访问隔离后缀限制绕过说明服务端虽然设置了访问限制但限制逻辑不严谨。5. 本节小结本实验不是单纯证明“某个文件能被下载”而是通过对比验证了两个现象1. 公开文件 legal.md 可以访问这是正常基线 2. 敏感文件 acquisitions.md 也可以访问说明存在敏感文件暴露 3. package.json.bak 直接访问被拒绝说明服务端存在后缀限制 4. package.json.bak%2500.md 访问成功说明该限制可以被绕过。因此该漏洞的核心可以概括为服务端没有合理区分公开文件和敏感文件 并且文件后缀限制逻辑存在绕过空间。三 源码分析与修复建议1. 源码定位在 Juice Shop 的 challenge 页面中可以看到与/ftp/文件访问相关的路由配置/* /ftp directory browsing and file download */app.use(/ftp,serveIndexMiddleware,serveIndex(ftp,{icons:true}))app.use(/ftp/:file,servePublicFiles())app.use(/ftp/quarantine/:file,serveQuarantineFiles())这几行代码分别对应前面实验中的现象源码逻辑对应实验现象app.use(/ftp, serveIndex(...))访问/ftp/时返回目录文件列表app.use(/ftp/:file, servePublicFiles())访问/ftp/legal.md、/ftp/acquisitions.md、/ftp/package.json.bak等文件servePublicFiles()对具体文件访问进行校验与返回因此本案例的核心源码位置集中在两部分1. /ftp 目录浏览配置 2. /ftp/:file 文件访问处理逻辑2. 目录浏览问题分析源码中存在如下配置app.use(/ftp,serveIndexMiddleware,serveIndex(ftp,{icons:true}))该配置会将ftp目录以目录列表的形式展示给浏览器。这与前面实验中访问http://localhost:3000/ftp/后看到文件列表的现象一致。从安全角度看问题在于目录浏览功能暴露了 /ftp/ 目录下的文件名称 用户可以直接看到 legal.md、acquisitions.md、package.json.bak 等文件。这为后续访问敏感文件提供了路径线索。因此acquisitions.md被发现并访问并不是因为复杂扫描而是因为服务端主动暴露了目录列表。3. 敏感文件暴露原因分析源码中/ftp/:file路由会处理具体文件访问app.use(/ftp/:file,servePublicFiles())前面实验中访问/ftp/legal.md返回200 OK这是正常公开文件访问。但继续访问/ftp/acquisitions.md同样返回200 OK并且文件内容中明确包含This document is confidential! Do not distribute!这说明问题不只是“文件可以访问”而是公开文件和敏感文件被放在了同一个可访问目录中 服务端没有进一步区分哪些文件可以公开访问哪些文件不应该公开。也就是说servePublicFiles()在处理文件访问时允许用户直接读取/ftp/目录下的敏感文档。4. 后缀限制逻辑分析前面实验中直接访问/ftp/package.json.bak服务端返回403 Forbidden Only .md and .pdf files are allowed!同时错误堆栈中出现at verify (/juice-shop/build/routes/fileServer.js:69:18) at /juice-shop/build/routes/fileServer.js:53:13这说明servePublicFiles()内部存在一个verify校验逻辑用于判断文件类型是否允许访问。从实验结果可以推断该校验逻辑至少做了类似判断只允许访问 .md 和 .pdf 文件所以/ftp/legal.md → 允许访问 /ftp/acquisitions.md → 允许访问 /ftp/package.json.bak → 拒绝访问这说明服务端确实设置了后缀限制。5. 后缀限制绕过原因分析虽然直接访问.bak文件会被拒绝但前面实验中访问/ftp/package.json.bak%2500.md服务端返回200 OK并成功读取到了package.json.bak的实际内容。这说明当前后缀限制存在问题校验阶段认为请求路径符合 .md 后缀要求 但实际读取阶段读取到的却是 package.json.bak 文件。因此本案例的绕过原因可以概括为服务端后缀校验逻辑与实际文件读取逻辑不一致。也就是说服务端不能只根据用户请求路径的表面后缀判断文件是否安全而应该基于最终解析后的真实文件路径进行校验。6. 修复建议针对本案例可以从三个方面进行修复。6.1 关闭不必要的目录浏览如果业务不需要展示/ftp/目录文件列表应避免使用目录浏览功能。问题代码app.use(/ftp,serveIndexMiddleware,serveIndex(ftp,{icons:true}))修复思路不要直接暴露 /ftp/ 目录列表。 用户不应该通过目录浏览看到服务器上的全部文件名。可以改为只提供明确的文件下载接口 不提供目录索引页面。6.2 将敏感文件移出 Web 可访问目录acquisitions.md这类文件不应与legal.md放在同一个公开目录中。修复思路legal.md 这类公开文件可以放在公开目录 acquisitions.md、package.json.bak 等敏感文件不应放在 /ftp/ 目录下。也就是说公开文件和内部文件应物理隔离。示例public-files/ legal.md private-files/ acquisitions.md package.json.bak其中private-files/不应被 Web 服务器直接暴露。6.3 使用白名单控制可访问文件当前问题的根源之一是用户可以直接通过文件名访问/ftp/目录下的资源。更安全的做法是使用服务端白名单。例如只允许访问明确公开的文件legal.md而不是允许用户传入任意文件名。修复思路可以表示为用户请求文件 ↓ 服务端检查文件是否在白名单中 ↓ 只有白名单文件才允许返回伪代码示例constallowedFiles[legal.md]functionisAllowedFile(file){returnallowedFiles.includes(file)}如果请求的文件不在白名单中则直接拒绝403 Forbidden6.4 基于最终解析路径进行校验对于后缀绕过问题不能只判断用户传入的原始字符串是否以.md或.pdf结尾。修复时应保证校验对象 最终实际读取的文件路径修复思路接收用户请求路径 ↓ 进行 URL 解码和路径规范化 ↓ 得到最终真实文件路径 ↓ 判断该路径是否位于允许目录内 ↓ 判断该文件是否在白名单中 ↓ 返回文件重点是不要让“校验时看到的路径”和“实际读取的路径”不一致。7. 本节小结结合源码和实验现象本案例的问题可以总结为1. /ftp/ 开启了目录浏览导致文件列表暴露 2. 敏感文件 acquisitions.md 被放在可公开访问目录中 3. 服务端虽然限制了 .bak 文件访问但后缀校验逻辑可以被绕过 4. 根本问题是文件资源缺少严格的访问边界和白名单控制。对应修复思路为1. 关闭目录浏览 2. 敏感文件不要放在 Web 可访问目录 3. 使用白名单控制可访问文件 4. 对最终解析后的真实路径进行校验 5. 不依赖简单后缀判断作为唯一防护。