当前位置: 首页 > news >正文

Java Web路径穿越漏洞实战:从WEB-INF泄露到安全防御

1. 项目概述:一次典型的Java Web路径穿越漏洞实战复盘

最近在整理过去的CTF解题笔记,翻到了这道来自RoarCTF 2019的“Easy Java”。这道题在当年算是一个经典的Java Web路径穿越漏洞案例,它巧妙地利用了Java Web应用对文件下载功能的不安全实现,结合对WEB-INF目录结构的理解,来获取敏感的配置文件。很多刚接触Java Web安全的同学,可能对WEB-INF这个目录的“神圣不可侵犯”性有误解,觉得它被容器保护得很好,这道题就是一个很好的“祛魅”过程。它不涉及复杂的框架漏洞或反序列化,核心就是最基础的路径遍历(Path Traversal)和对Web应用标准目录结构的认知。通过复现这道题,我们能清晰地看到,一个看似简单的文件下载接口,如果缺乏有效的输入校验和路径控制,会带来多么严重的信息泄露风险。无论你是正在打CTF的新手,还是想巩固Web安全基础的开发者,这个案例都值得深入琢磨一下。

2. 漏洞原理与场景深度拆解

2.1 核心漏洞:不受控的文件路径参数

这道题目的场景非常明确:一个提供了文件下载功能的Java Web应用。通常,前端会有一个列表或链接,点击后触发类似/download?filename=xxx.pdf的请求,后端则根据这个filename参数去服务器特定目录(比如/files)读取文件并返回。

漏洞的根源就在于,这个filename参数完全由用户控制,并且后端程序在拼接文件路径时,没有进行任何规范化(Canonicalization)和校验。攻击者可以注入包含路径遍历序列(如../)的字符串。

例如,假设后端代码是这样写的(概念性代码):

String basePath = "/var/www/app/uploads/"; String filename = request.getParameter("filename"); File file = new File(basePath + filename); // ... 读取文件并输出给用户

如果用户传入filename=../../../etc/passwd,那么最终拼接的路径就变成了/var/www/app/uploads/../../../etc/passwd,经过系统路径解析后,就等价于/etc/passwd。这就实现了跨越应用目录,读取服务器上任意文件的目的。

注意:在Unix-like系统和Windows系统上,路径遍历的表示方法略有不同(../vs..\),但原理一致。Java的File类会处理这些序列。

2.2 关键目标:WEB-INF目录与web.xml

在标准的Java Web应用(遵循Servlet规范)中,WEB-INF是一个位于应用根目录下的特殊目录。它的特殊性在于:

  1. 客户端不可直接访问:Servlet容器(如Tomcat, Jetty)会阻止任何直接来自客户端的对WEB-INFMETA-INF目录下资源的请求。你无法通过http://target.com/app/WEB-INF/web.xml直接访问到它。
  2. 存放核心配置与代码WEB-INF目录下通常包含:
    • web.xml:Web应用部署描述文件,是核心配置文件,定义了Servlet、Filter、Listener等。
    • classes/:存放编译后的Java类文件(.class)。
    • lib/:存放应用依赖的JAR包。

正因为客户端无法直接访问,一些开发者会误以为其中的文件是绝对安全的。然而,如果存在上述的路径穿越漏洞,并且应用本身有权限读取这些文件,那么WEB-INF的“保护”就形同虚设了。

web.xml文件是本题的“Flag”所在。它里面可能包含数据库连接信息、敏感接口路径、甚至是后端的逻辑密码(在这道CTF题中,flag很可能就以注释或某个参数值的形式藏在里面)。通过路径穿越读取web.xml,是Java Web安全信息收集中非常经典的一步。

2.3 场景还原:题目可能的界面与交互

根据“Easy Java”这个名称和常见出题套路,我们可以推测题目环境可能提供了一个非常简单的Web界面。也许是一个“帮助”页面,里面提了一下有个文件下载功能;或者更直接,页面上就有一个输入框,写着“输入文件名下载”,旁边放个“Download”按钮。

初始尝试可能是下载一个已知的、正常的文件,比如help.pdfreadme.txt,以确认下载功能正常工作。然后,攻击的思路便转向:我能否利用这个功能,去读取下载目录之外的文件?特别是那个受保护的WEB-INF/web.xml文件。

这里就引出一个关键问题:我们知道目标文件是WEB-INF/web.xml,但我们从哪个起点开始穿越呢?我们需要知道文件下载功能设置的基准目录(basePath)在哪里。是应用根目录?还是某个子目录?这通常需要一些探测或猜测。

3. 解题步骤与实操过程详解

3.1 信息收集与功能探测

第一步永远是观察。访问目标网址,查看页面源码、JavaScript文件、以及可能的注释。题目可能在前端给出提示,比如注释里写着“下载功能仅供下载/doc目录下的文件”。

更重要的步骤是直接测试下载接口。通过浏览器开发者工具的Network面板,观察点击下载按钮时发出的请求。假设我们捕获到的请求是:

GET /download?filename=help.doc HTTP/1.1

这就确认了接口路径和参数名。

手动修改参数进行测试:

  1. 测试基础遍历:尝试filename=../../../。观察响应。如果是目录列表泄露,可能会返回错误信息或403/500状态码。如果服务器配置了默认索引页,可能返回200但内容是索引页。最理想的情况是返回一个包含WEB-INF的目录列表。
  2. 测试绝对路径:有些情况下,如果后端直接使用filename参数创建File对象,甚至可能支持绝对路径(如filename=/etc/passwd),但这道Java题大概率不支持。

3.2 构造路径穿越Payload

这是最核心的一步。我们需要找到从下载功能的基准目录到WEB-INF/web.xml的相对路径。

在Java Web应用中,一个Servlet的当前工作目录(ServletContext的根路径)通常是Web应用的根目录。如果下载Servlet没有特意设置basePath,而是直接使用filename,那么new File(filename)就会相对于应用根目录(或者说是Servlet容器的当前工作目录,但通常是应用根目录)来寻找文件。

一个经典的Payload是filename=WEB-INF/web.xml如果下载Servlet的路径解析是相对于应用根目录的,那么这个请求就会直接尝试读取应用根目录下的WEB-INF/web.xml文件。

但更常见的情况是,下载功能被限制在某个子目录,比如/files/downloads。这时,我们就需要向上回退。

假设基准目录是/var/www/tomcat/webapps/ctfapp/downloads/,而web.xml/var/www/tomcat/webapps/ctfapp/WEB-INF/web.xml。 那么,从downloads目录回到应用根目录,需要../。再从根目录进入WEB-INF,所以完整的相对路径是:../WEB-INF/web.xml

因此,Payload尝试顺序通常是:

  1. WEB-INF/web.xml(直接读取)
  2. ../WEB-INF/web.xml(回退一层)
  3. ../../WEB-INF/web.xml(回退两层)
  4. ../../../WEB-INF/web.xml(回退三层)

在实战或CTF中,可能需要多次尝试。你可以通过不断添加../来向上遍历,直到读到文件或返回错误。

3.3 利用漏洞读取web.xml

当我们构造出正确的Payload,比如filename=../../../WEB-INF/web.xml(假设需要回退三层),并向/download接口发起请求时,如果漏洞存在且路径正确,服务器就不会返回一个文件下载,而是直接将web.xml的内容输出到HTTP响应体中。

实操记录: 使用curl工具或浏览器直接访问构造的URL:

curl 'http://target-ctf-server:port/download?filename=../../../WEB-INF/web.xml'

或者使用Burp Suite的Repeater模块,手动修改并重放请求。

成功的响应特征

  • 状态码:通常是200 OK。
  • 响应头Content-Type可能是application/xmltext/xml或者甚至是application/octet-stream(如果后端没有正确设置MIME类型)。
  • 响应体:直接就是web.xml文件的XML格式内容。

这时,你需要仔细查看这个XML文件的内容。Flag可能以以下几种形式存在:

  1. 作为注释<!-- flag{this_is_the_flag} -->
  2. 作为某个初始化参数的值<param-value>flag{config_password_here}</param-value>
  3. 作为某个Servlet或Filter的名称:虽然不常见。
  4. 藏在文件末尾或某个不起眼的配置项里

3.4 扩展利用:读取Class文件与源码泄露

拿到web.xml后,解题可能就结束了。但作为一次完整的学习,我们可以思考更深层次的利用。web.xml里定义了Servlet和其对应的处理类。例如:

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.ctfapp.servlet.LoginServlet</servlet-class> </servlet>

我们知道了处理登录的类文件是com.ctfapp.servlet.LoginServlet。这个类文件编译后位于WEB-INF/classes/目录下,对应的路径是WEB-INF/classes/com/ctfapp/servlet/LoginServlet.class

既然我们已经有了路径穿越漏洞,我们完全可以尝试去读取这个.class文件:filename=../../../WEB-INF/classes/com/ctfapp/servlet/LoginServlet.class

.class文件是字节码,我们可以使用反编译工具(如JD-GUI、CFR、FernFlower)将其还原成Java源代码。在真实的渗透测试或更复杂的CTF题中,这可能会泄露关键的业务逻辑、加密算法、硬编码的密钥等,为进一步攻击(如逻辑漏洞、反序列化)铺平道路。

4. 漏洞挖掘与防御的深层思考

4.1 为什么这种漏洞会发生?

从开发角度,原因无非以下几点:

  1. 安全意识不足:开发者认为文件名参数是前端可控的,或者只会在预设列表中选择,忽视了用户可直接修改HTTP请求。
  2. 对Java File API的误解:认为new File()只会在当前目录下操作,或者不了解路径遍历序列的威力。
  3. 缺乏输入校验:没有对用户输入的filename参数进行“白名单”校验(只允许特定文件名)或“规范化+校验”处理。
  4. 框架误用:可能使用了某些框架的便捷方法,但没有仔细阅读文档,不知道这些方法可能存在安全风险。

4.2 如何防御路径穿越漏洞?

防御的核心原则是:永远不要信任用户输入,对文件路径进行严格的白名单控制。

  1. 白名单校验:这是最有效的方法。如果下载功能只允许下载少数几个已知文件,那么直接维护一个允许的文件名列表(Map),将用户输入的参数与列表比对,只返回匹配的文件。

    Map<String, String> allowedFiles = new HashMap<>(); allowedFiles.put("guide", "/secure/path/guide.pdf"); allowedFiles.put("help", "/secure/path/help.doc"); String fileKey = request.getParameter("key"); // 不用filename了,用key String realPath = allowedFiles.get(fileKey); if (realPath == null) { // 返回错误,文件不存在 return; } File file = new File(realPath);
  2. 路径规范化与校验:如果必须支持一定动态性,则:

    • 规范化路径:使用File.getCanonicalPath()Path.normalize().toAbsolutePath()来获取规范化的绝对路径。
    • 校验路径前缀:确保规范化后的路径,是以你允许的基准目录(BASE_DIR)开头的。
    String userInput = request.getParameter("filename"); // 定义允许的基准目录 File baseDir = new File("/var/www/app/safe_download_area"); // 构造用户请求的文件 File requestedFile = new File(baseDir, userInput); // 获取规范路径 String canonicalPath = requestedFile.getCanonicalPath(); // 检查规范路径是否以基准目录的规范路径开头 if (!canonicalPath.startsWith(baseDir.getCanonicalPath() + File.separator)) { // 路径穿越尝试!拒绝请求。 throw new IllegalArgumentException("Invalid file path."); } // 安全,可以读取文件
  3. 使用资源ID而非路径:在数据库中存储文件,前端通过文件ID(如UUID)来请求下载,后端根据ID从数据库或安全存储中获取文件流。

  4. Web服务器配置:在Nginx或Apache层面,可以配置规则阻止请求中包含../的URL。

4.3 CTF中的变体与进阶思考

在更复杂的题目中,出题人可能会设置一些障碍:

  • 过滤../:后端代码可能用replaceAll("\\.\\./", "")replace("\\.\\./", "")来过滤../。但可能存在双写绕过(....//过滤一次后变成../)或使用URL编码(%2e%2e%2f)绕过。
  • 强制添加后缀:后端可能自动为输入添加.pdf后缀。这时需要利用%00(空字节截断)或路径参数(filename=../../../WEB-INF/web.xml%00),但在高版本JDK和Servlet容器中,空字节截断通常已失效。另一种思路是考虑目录遍历后,目标文件本身是否需要后缀。
  • 读取其他敏感文件:除了web.xml,还可以尝试读取WEB-INF/classes/下的.class文件、/etc/passwd/proc/self/environ(Linux环境变量)、应用日志文件等,进行信息收集。

复现这道“Easy Java”题目,绝不仅仅是为了得到一个Flag。它像一把钥匙,打开了Java Web应用安全中“访问控制”和“输入校验”这两扇最基础也最重要的大门。理解了路径穿越,你就能举一反三,在遇到文件上传、文件包含、模板注入等其他漏洞时,拥有更敏锐的嗅觉。下次当你看到任何一个由用户输入控制的文件路径参数时,心里都应该立刻响起警报:这里,会不会是下一个“Easy Java”?

http://www.jsqmd.com/news/1100573/

相关文章:

  • 无犯罪记录公证书需要什么材料?无犯罪记录公证多久拿到?
  • 车载音乐下载 | 2026年更新最全网盘资源转存免费下载分享+副业变现方法
  • 淘宝拍立淘图片搜索API完整文档
  • Web应急响应实战:从入侵排查到溯源加固的完整指南
  • QT常用控件篇(3)(上)
  • 外卖退潮与AI浪潮:2026年餐饮业运营逻辑的艰难重构
  • 基础控件的信号:
  • 靠谱的装修公司哪家专业
  • 哑光亮调lr预设|高级哑光柔焦人像写真Lightroom下载lr调色风格
  • 给国产大模型 Agent 一副身体:我用魔珐星云搭建具身交互智能数字人
  • 广货行天下!超高清供需会现场体验VEGA H2
  • 从 Token Approval 到权限撤销:自托管钱包授权管理实践
  • 【华为OD机试真题 新系统】1034、数据包分段传输的最小最大延迟 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)
  • 我把橘子洲头做成了AI客服:本地大模型落地的第一个真实场景
  • DCMTK:如何构建医疗影像系统的完整解决方案?
  • 【Claude Code】----Claude Code 23个高效技巧,效率拉满!!
  • 普通人靠挖漏洞也能高薪?揭秘白帽黑客 5K 到 13.2W 收入蜕变全过程,梳理合法变现全部渠道
  • 企业级AI改造实战:Agent、RAG与MCP组合拳破解复杂系统知识鸿沟
  • AI代理运行时解耦:会话即事件日志的工程实践
  • Codex客户端插件推荐:TOP 10 插件盘点,新手和开发者都值得收藏
  • 【稀缺干货】VMware KB 81992原始补丁分析:精简磁盘在vSAN 8.0U2中触发SCSI Reservation Timeout的底层链路图解
  • OPID:在线策略技能蒸馏,让智能体学习无需外部记忆
  • 低端手机评论发表速度------目前发表评论速度有点慢-----可以提高
  • VisualCppRedist AIO:一键修复Windows软件兼容性问题的终极解决方案
  • 日用五金注塑模具,性价比真的能打吗?
  • Minecraft服务器NPC插件终极指南:Citizens2完整入门与实践
  • HarmonyOS技术精讲-Image Kit:初识图片处理服务 - 核心概念与架构解析
  • Codex客户端必备插件TOP10推荐:系统自动化、Figma、GitHub、PPT一站式搞定(2026最新版)
  • FanControl深度解析:打造Windows系统智能散热控制方案
  • DCMTK深度解析:医疗影像开发的完整解决方案实战指南