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

Tomcat文件包含漏洞深度解析:从原理到防御的实战指南

1. 项目概述:为什么我们要关注Tomcat文件包含漏洞?

如果你是一名Web应用开发者、安全研究员或者运维工程师,那么“Apache Tomcat文件包含漏洞”这个词组对你来说绝对不陌生。它不像那些惊天动地的远程代码执行漏洞那样引人注目,但在实际的渗透测试和红蓝对抗中,文件包含漏洞往往是打开内网大门的“第一把钥匙”。我见过太多案例,一个看似不起眼的文件包含点,最终演变成整个内网沦陷的起点。今天,我们就来深入拆解这个漏洞,不仅仅是复现,更要理解它的前世今生、触发条件、利用手法,以及最关键的——如何防御。

简单来说,文件包含漏洞允许攻击者通过Web应用的参数,动态包含并执行服务器上的任意文件。在Tomcat的语境下,这通常与特定的配置错误、老旧版本的缺陷,或者应用程序自身的逻辑问题紧密相关。复现这个漏洞,不仅能让你直观感受到攻击链是如何串联的,更能让你在开发或运维时,下意识地避开那些“坑”。网上教程很多,但大多只给步骤,不讲原理。我的目标是带你走一遍我踩过的路,把每个参数、每个报错背后的逻辑都讲清楚。

2. 漏洞原理深度解析:不仅仅是“包含”那么简单

2.1 文件包含漏洞的核心机制

要理解Tomcat下的文件包含,我们得先抛开Tomcat,看看文件包含漏洞的通用原理。它主要分为两种:本地文件包含(LFI)和远程文件包含(RFI)。LFI允许攻击者包含并读取服务器本地的文件,比如/etc/passwd、应用源码、配置文件等。而RFI则更危险,攻击者可以包含一个远程服务器上的恶意文件(如一个包含PHP代码的文本文件),并让目标服务器执行其中的代码。

在Java Web应用中,文件包含通常发生在使用RequestDispatcher.include()RequestDispatcher.forward()方法时,如果开发者未经严格过滤就将用户输入(如请求参数)直接拼接到文件路径中,漏洞就产生了。例如,一个JSP页面有这样一段代码:

<% String page = request.getParameter("page"); if (page != null) { request.getRequestDispatcher("/pages/" + page + ".jsp").include(request, response); } %>

攻击者只需要构造参数page=../../../WEB-INF/web.xml,就有可能穿越目录,读取到Web应用的敏感配置文件web.xml

2.2 Tomcat环境下的特殊性

Tomcat作为Servlet容器,其自身的一些特性和历史配置会加剧文件包含的风险:

  1. 默认Servlet的配置:Tomcat的默认Servlet(DefaultServlet)负责处理静态资源。在某些特定配置下(如readonly设置为false且未做严格路径限制),可能会被滥用来进行文件读取。
  2. JSP预编译与包含:JSP文件在首次被访问时会被Tomcat编译成Servlet。<%@ include file=”...” %>这类静态包含指令,如果文件路径可控,就可能造成LFI。虽然动态包含(<jsp:include page=”...” />)更常见,但原理相似。
  3. 老旧版本与特定模块:历史上,Tomcat某些版本与第三方库(如某些版本的Apache JServ Protocol连接器)结合时,存在可被利用的文件包含缺陷。此外,像CVE-2020-1938(Ghostcat)这类与AJP协议相关的漏洞,虽然本质是文件读取/包含,但利用方式完全不同。

我们本次复现聚焦于由应用逻辑缺陷引发的本地文件包含(LFI),这是开发中最容易不小心引入,也最具有普遍教育意义的类型。理解了这个,你就能举一反三。

注意:切勿在非授权环境中进行任何漏洞测试。所有实验必须在你自己完全控制的、隔离的虚拟机或实验环境中进行。

2.3 与网络热词中的其他漏洞对比

你可能会看到“永恒之蓝”、“Shiro反序列化”这些更“炫酷”的漏洞。文件包含漏洞通常被认为是“低危”的,因为它可能“只是”读取文件。但这是一个严重的误区。通过LFI,攻击者可以:

  • 读取配置文件:获取数据库密码、API密钥、加密盐值。
  • 读取源码:进行白盒审计,发现更深的逻辑漏洞。
  • 结合其他漏洞:例如,在知道绝对路径后,配合文件上传漏洞,就能实现从LFI到RCE(远程代码执行)的质变。或者,在某些特定环境下(如php://等包装器在特定配置下可用),LFI可以直接转化为代码执行。 因此,永远不要小看一个文件包含点。

3. 实验环境搭建与漏洞应用准备

3.1 靶机环境配置

为了原汁原味地复现,我们手动搭建一个存在漏洞的简单Web应用,而不是使用现成的漏洞靶场。这样你能更清楚地看到漏洞是如何被“编码”进去的。

1. 基础环境:

  • 操作系统:Ubuntu 22.04 LTS 或 Windows 10/11。我推荐使用Linux,路径处理更清晰。这里以Ubuntu为例。
  • Java环境:安装JDK 8或11。Tomcat对JDK版本有要求,建议使用LTS版本。
    sudo apt update sudo apt install openjdk-11-jdk java -version # 验证安装
  • Apache Tomcat:从 Apache Tomcat官网 下载Tomcat 9.x版本。选择tar.gz包。
    wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85.tar.gz tar -xzf apache-tomcat-9.0.85.tar.gz mv apache-tomcat-9.0.85 ~/tomcat9 cd ~/tomcat9

2. 创建漏洞应用:~/tomcat9/webapps/目录下,新建一个文件夹vuln-app

mkdir ~/tomcat9/webapps/vuln-app mkdir ~/tomcat9/webapps/vuln-app/WEB-INF mkdir ~/tomcat9/webapps/vuln-app/WEB-INF/classes

创建web.xml,这是应用的部署描述符。

nano ~/tomcat9/webapps/vuln-app/WEB-INF/web.xml

输入以下内容:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>Vulnerable File Include App</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

现在,创建存在漏洞的JSP页面index.jsp

nano ~/tomcat9/webapps/vuln-app/index.jsp

输入以下漏洞代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件包含漏洞演示</title> </head> <body> <h2>文件包含演示页面</h2> <% // 漏洞点:直接获取用户输入并用于文件包含,未做任何过滤和路径校验 String file = request.getParameter("file"); if (file != null) { try { // 使用RequestDispatcher进行包含,这是Servlet标准API request.getRequestDispatcher(file).include(request, response); } catch (Exception e) { out.println("<p style='color:red;'>包含文件时出错: " + e.getMessage() + "</p>"); } } else { out.println("<p>请通过?file=参数指定要包含的文件。</p>"); } %> <hr> <p>示例(正常功能):<a href="?file=/header.jsp">?file=/header.jsp</a> (假设存在)</p> <p>示例(漏洞利用):<a href="?file=../../../../etc/passwd">?file=../../../../etc/passwd</a></p> </body> </html>

再创建一个正常的被包含文件header.jsp,放在应用根目录:

nano ~/tomcat9/webapps/vuln-app/header.jsp
<h3>这是正常的页眉文件</h3> <p>当前时间:<%= new java.util.Date() %></p>

3.2 启动Tomcat并验证

进入Tomcat的bin目录,启动它:

cd ~/tomcat9/bin ./startup.sh # Windows下使用 startup.bat

如果看到Tomcat started.类似的日志,说明启动成功。默认服务运行在8080端口。

打开浏览器,访问http://你的服务器IP:8080/vuln-app/。 你应该能看到我们的演示页面。先点击“?file=/header.jsp”链接,页面会正常显示页眉内容和时间。这说明包含功能在工作。

4. 漏洞复现与利用实战

环境就绪,现在让我们开始“攻击”自己搭建的这个脆弱应用。

4.1 基础利用:读取敏感系统文件

点击页面上给出的漏洞利用链接?file=../../../../etc/passwd,或者直接在地址栏手动构造。

http://localhost:8080/vuln-app/?file=../../../../etc/passwd

发生了什么?

  1. 我们的应用在/tomcat9/webapps/vuln-app/目录下。
  2. 参数file的值为../../../../etc/passwd
  3. request.getRequestDispatcher(file)会基于当前请求的上下文路径(/vuln-app)来解析这个相对路径。
  4. ../意味着向上级目录跳转。从vuln-app目录开始:
    • 第一个../:跳到webapps
    • 第二个../:跳到tomcat9
    • 第三个../:跳到tomcat9的父目录(通常是用户主目录)
    • 第四个../:跳到根目录/
    • 然后拼接etc/passwd,最终尝试包含/etc/passwd文件。

如果服务器是Linux且Tomcat进程有读取权限,你将在网页上看到/etc/passwd文件的内容。这就是最经典的本地文件包含。

实操心得:路径穿越的“深度”需要多少个../?这取决于你的Web应用在服务器文件系统中的实际深度。我常用的方法是先尝试../../../,如果返回“文件未找到”或类似错误,再逐步增加../的数量。也可以尝试绝对路径,如file=/etc/passwd,但RequestDispatcher通常只处理相对上下文路径或绝对路径(以/开头,但仍在应用上下文内),直接根目录绝对路径可能被安全策略拦截或解析失败,但相对路径穿越更通用。

4.2 进阶利用:读取Web应用自身配置文件

对于攻击者而言,读取系统文件固然好,但读取应用自身的配置文件往往能获得更直接的收益。让我们尝试读取该Web应用的web.xml文件。

http://localhost:8080/vuln-app/?file=../WEB-INF/web.xml

解析:

  • 当前上下文是/vuln-app
  • ../WEB-INF/web.xml会跳转到vuln-app的上级目录webapps,然后寻找WEB-INF/web.xml?不对!
  • 这里有个关键点:WEB-INF是一个受Tomcat保护的特殊目录。客户端无法直接通过URL访问其下的任何文件。但是,通过服务器端的请求分发(RequestDispatcher)是可以访问到的
  • 正确的路径是:/WEB-INF/web.xml(绝对路径,相对于应用上下文)。或者,因为我们的漏洞页面就在应用根目录,所以WEB-INF/web.xml(相对路径)也可以。让我们试试:
http://localhost:8080/vuln-app/?file=WEB-INF/web.xml

你会发现,成功读取到了我们之前编写的web.xml的内容。如果这个文件里定义了数据库连接池参数,那么数据库用户名和密码就泄露了。

重要注意事项WEB-INFMETA-INF目录是Java Web应用的安全边界。RequestDispatcher可以访问它们,但外部请求不能。这意味著,一个LFI漏洞赋予了攻击者“内部视角”,这是非常危险的。

4.3 利用漏洞获取源码(.java/.class)

假设我们的应用有一个Servlet,编译后的class文件位于WEB-INF/classes/com/example/MyServlet.class。攻击者虽然无法直接下载.class文件,但可以通过LFI让服务器将其内容作为文本或二进制流包含到响应中。由于class文件是二进制,直接包含可能会显示乱码,但通过一些技巧(如利用某些编码或错误页面的差异),可以推断信息。

更致命的是,如果服务器配置了JSP文件未预编译即暴露源码(某些老旧或错误配置),攻击者可能通过包含.jsp文件直接看到源码。例如,尝试包含自身:

http://localhost:8080/vuln-app/?file=index.jsp

这通常会执行JSP,而不是显示源码。但在特定条件下(如请求.jsp文件时加上某些特殊参数,或在某些中间件配置下),可能会以源码形式返回。

4.4 从LFI到RCE的尝试

这是文件包含漏洞的“终极形态”。在PHP中,常结合php://input等包装器实现。在Java中,直接通过LFI执行代码比较困难,但并非不可能,通常需要结合其他漏洞:

  1. 结合文件上传:这是最常见的链。如果网站同时存在文件上传漏洞,允许上传JSP文件(或可被Tomcat解析的恶意文件),并且攻击者通过LFI知道了上传文件的绝对路径,那么就可以直接包含该上传文件,从而执行任意代码。
  2. 利用服务器特定文件:包含服务器上已有的、内容部分可控的文件。例如,包含Tomcat的日志文件(../logs/localhost_access_log.*.txt),如果攻击者能将恶意代码(如JSP标签)注入到User-Agent或URL中(需要URL编码),那么日志里就会记录这些代码。再通过LFI包含这个日志文件,Tomcat可能会将其作为JSP解析执行。这种方法对Tomcat版本和配置非常敏感,成功率不高,但理论存在
  3. 利用/proc目录(Linux):在Linux系统上,/proc/self/environ文件包含了当前进程的环境变量。如果环境变量中有用户可控的部分(在某些CGI模式下可能),或许能注入代码。但这同样非常苛刻。

演示一个概念性尝试(通常失败,但用于理解思路):假设我们能让Tomcat将我们的请求参数记录到日志,并且日志文件可被包含。我们构造一个特殊的请求:

http://localhost:8080/vuln-app/?file=../logs/localhost_access_log.2024-12-01.txt&<%Runtime.getRuntime().exec("touch /tmp/pwned");%>

然后访问这个URL。即使失败,这个过程也展示了攻击者的思路:寻找一切可能将代码写入服务器文件系统,再通过LFI去触发它。

5. 漏洞挖掘与排查技巧实录

在实际的安全评估中,你不太可能遇到一个如此明显的、把参数名直接叫file的漏洞。更多时候,你需要去挖掘和判断。

5.1 如何寻找文件包含点

  1. 参数分析:关注所有可能表示文件、页面、模板、模块的请求参数。常见参数名有:file,page,path,template,module,include,load,document,view,f等。
  2. 功能推测:在网站中寻找“动态加载内容”的功能。比如,一个页面有多个标签页,切换时URL参数变化;或者有“下载”、“预览”功能,可能会涉及文件路径。
  3. 错误信息:尝试在参数中输入一些特殊值(如../../../../),观察服务器的错误响应。如果错误信息中提到了“文件未找到”、“路径错误”等,而不是“参数非法”,那这里就可能存在路径遍历或文件包含。
  4. 模糊测试(Fuzzing):使用工具(如Burp Suite的Intruder)或自定义字典,对目标参数进行大量路径遍历payload的测试。字典应包含各种深度和不同操作系统的路径模式。

5.2 手工测试Payload库

以下是我积累的一些常用测试payload,你可以根据实际情况调整:

  • 基础路径遍历
    • ../../../../etc/passwd
    • ....//....//....//....//etc/passwd(双重编码或特殊绕过)
    • /etc/passwd(绝对路径)
    • file:///etc/passwd(如果URL处理逻辑支持file协议)
  • Web应用相关
    • WEB-INF/web.xml
    • ../WEB-INF/web.xml
    • WEB-INF/classes/application.properties(Spring Boot配置)
    • index.jsp(尝试读取源码)
  • Windows系统
    • ..\..\..\..\windows\win.ini(使用反斜杠)
    • ../../../../windows/system32/drivers/etc/hosts
  • 日志文件
    • ../logs/catalina.out
    • ../logs/localhost_access_log.%日期%.txt
    • ../../apache-tomcat-9.0.85/logs/localhost_access_log.2024-12-01.txt

5.3 常见拦截与绕过技巧

现代WAF(Web应用防火墙)和安全框架会对路径遍历进行过滤。以下是一些可能的绕过姿势:

  1. 编码绕过
    • URL编码:..%2f..%2f..%2f..%2fetc%2fpasswd(将/编码为%2f)
    • 双重URL编码:..%252f..%252f..%252f..%252fetc%252fpasswd
    • Unicode编码:在某些解析场景下可能有效。
  2. 特殊字符绕过
    • 使用....//..\/等变体。
    • 在路径末尾添加空字节%00(空字节截断,在老旧Java版本或特定场景下可能有效,用于截断文件扩展名)。例如:../../../etc/passwd%00.jpg,如果代码是拼接.jpg后缀的话。
  3. 路径标准化绕过:有些过滤器只检查../,但不会递归检查。..././...//经过路径标准化后可能变成../
  4. 协议包装器绕过(Java中较少见):如果应用逻辑直接使用new FileInputStream(param)等低级API,且未对输入做协议检查,理论上可能使用file://http://等。但在Servlet的RequestDispatcher中,通常不支持这些协议。

排查技巧实录:当你发现一个疑似包含点但被拦截时,不要轻易放弃。首先,用最简单的../../测试,看返回什么错误。是403、400,还是500?错误信息是否透露了后端技术(如Java堆栈跟踪)?然后,尝试将payload放在不同的参数位置,或者使用POST请求。有时,防护只针对GET请求。记录下所有不同的响应,这能帮你推测后端过滤逻辑的弱点。

6. 防御方案与安全开发实践

知道了怎么攻击,更重要的是知道如何防御。修复一个文件包含漏洞,通常比修复一个SQL注入要复杂一些,因为它涉及路径安全、输入验证和业务逻辑多个层面。

6.1 输入验证与白名单机制

最有效、最根本的防御措施。不要试图用黑名单过滤../绝对路径等,总有绕过的方法。

  • 方案:为filepage这类参数建立一个严格的白名单。只允许包含预定义的、安全的文件。
    // 安全代码示例 String requestedPage = request.getParameter("page"); Map<String, String> allowedPages = new HashMap<>(); allowedPages.put("home", "/templates/home.jsp"); allowedPages.put("news", "/templates/news.jsp"); allowedPages.put("about", "/templates/about.jsp"); String safePagePath = allowedPages.get(requestedPage); if (safePagePath != null) { request.getRequestDispatcher(safePagePath).include(request, response); } else { // 记录非法访问日志,并返回404或错误页面 response.sendError(HttpServletResponse.SC_NOT_FOUND); }
  • 为什么有效:攻击者无法提供白名单之外的任何值,从根本上杜绝了路径遍历。

6.2 路径规范化与校验

如果业务上确实需要动态包含文件(比如一个CMS系统),白名单不现实,那么必须进行严格的路径校验。

  • 方案
    1. 获取规范路径:使用java.io.FilegetCanonicalPath()方法,获取参数对应的绝对、规范、唯一的文件路径。
    2. 定义安全基准目录:明确指定允许包含的文件所在的根目录(例如/var/www/app/templates)。
    3. 校验是否在基准目录内:检查规范化的路径是否以基准目录的规范路径开头。
    // 安全代码示例 String baseDir = "/var/www/app/templates"; // 允许访问的根目录 String userInput = request.getParameter("template"); File base = new File(baseDir); File requestedFile = new File(base, userInput); // 将用户输入视为相对于baseDir try { String canonicalBasePath = base.getCanonicalPath(); String canonicalRequestedPath = requestedFile.getCanonicalPath(); // 关键校验:请求的文件路径必须在基准目录之下 if (canonicalRequestedPath.startsWith(canonicalBasePath + File.separator)) { // 安全,可以包含 request.getRequestDispatcher(userInput).forward(request, response); } else { throw new SecurityException("路径遍历攻击尝试: " + userInput); } } catch (IOException e) { // 处理异常 response.sendError(HttpServletResponse.SC_BAD_REQUEST); }
  • 实操心得:一定要用startsWith(canonicalBasePath + File.separator),而不是startsWith(canonicalBasePath)。因为如果canonicalBasePath/var/www/app/templates,攻击者输入../../../etc/passwd,经过new File(base, input)getCanonicalPath()后,得到的路径可能是/etc/passwd,它不以/var/www/app/templates开头,校验失败。但如果攻击者输入templates/../../../../etc/passwd,规范化后可能是/var/www/app/etc/passwd,它/var/www/app/templates开头吗?不,它是以/var/www/app开头,所以仍然失败。但如果我们只用startsWith(canonicalBasePath),那么/var/www/app/templates_evil(如果存在)可能会被误判为合法。加上File.separator确保了子目录关系。

6.3 服务器与容器层加固

  1. 运行Tomcat的用户权限最小化:不要用root用户运行Tomcat。创建一个专用的、低权限的用户(如tomcat),并确保它只能访问必要的目录(如webapps,logs,temp)。这样即使LFI成功,能读取的文件范围也大大受限。
  2. 设置Tomcat的SecurityManager:启用Java SecurityManager,并配置严格的安全策略文件,限制代码对文件系统、网络等资源的访问。这对于生产环境是很好的实践,但配置较为复杂。
  3. 及时更新:保持Tomcat和JDK版本更新,及时修复已知的公开漏洞。虽然我们复现的是逻辑漏洞,但保持基础环境安全是底线。
  4. 审查第三方库:确保应用中使用的所有第三方库(如Apache Commons FileUpload, Spring框架等)都是最新安全版本,它们本身也可能存在路径遍历或相关漏洞。

6.4 安全开发生命周期(SDL)集成

将文件包含漏洞的防范意识融入开发流程:

  • 安全编码规范:在团队规范中明确禁止未经校验的动态文件包含操作。
  • 代码审计:在代码审查阶段,重点关注所有涉及文件路径拼接、动态加载资源的地方。
  • 自动化扫描:在CI/CD流水线中集成静态应用安全测试(SAST)工具,如SonarQube、Checkmarx等,它们可以自动识别潜在的路径遍历漏洞模式。
  • 渗透测试:定期对应用进行黑盒/白盒渗透测试,主动寻找此类漏洞。

文件包含漏洞就像系统的一道暗门,看起来不起眼,却可能通往核心地带。理解它的原理、掌握复现和利用的方法,最终是为了更好地关上这扇门。在安全的世界里,攻击者的视角是最好的防御教材。希望这篇详细的拆解,能让你下次在写代码时,对那个小小的request.getParameter多一份警惕。

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

相关文章:

  • 如何零基础管理SQLite数据库?DB Browser for SQLite为你提供可视化解决方案
  • 怪物猎人世界终极辅助神器:HunterPie完整使用教程
  • 三分钟上手:biliTickerBuy帮你轻松搞定B站会员购抢票难题
  • STM32与LARA-R6401 LTE模块的嵌入式通信实战
  • B站成分检测器:智能识别用户兴趣标签的浏览器扩展实战指南
  • Si4732与PIC18LF45K80在数字收音机设计中的优化实践
  • Windows系统文件archiveint.dll丢失找不到问题解决
  • 高性价比多通道信号采集方案:PCF8591与ATSAME70Q21B实战
  • 基于STM32单片机的温湿度报警系统 OLED彩屏环境温湿度检测2(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • 前线部署工程师:AI时代的技术与产业“跨界翻译官“
  • MuleSoft+LangChain企业级AI编排实战:让大模型走进真实业务流水线
  • 补全还是干扰:LLM 代码补全效率的量化评估方法
  • Asyncio 事件循环源码解析:从 epoll 到协程调度的底层执行链路
  • STM32F303RC与13DOF传感器融合开发指南
  • RocketMQ服务部署
  • Windows系统文件AppxPackaging.dll丢失找不到问题解决
  • 终极指南:如何在Windows上使用vJoy虚拟摇杆创建游戏控制器
  • PIC32MZ与74HC32实现2x2键盘高效控制方案
  • 直流电机静音控制:TB9051FTG与PIC18F87J10方案解析
  • ChatGPT一键生成PPT?真相来了(2024最新实测报告:17款模板+8类行业适配性数据)
  • 如何高效使用抖音下载神器:免费开源工具实现高清无水印批量下载
  • 别再卷框架API:2026年Agent开发的五个持久“原语”
  • 数学分析完整知识点中英对照手册(全册完整版)
  • STM32与13DOF传感器的高精度定位系统设计
  • 国产芯片上的推理性能调优:昇腾950与GLM-5.2的适配实战
  • GitLab高危漏洞CVE-2024-6385深度剖析:从原理到防御实战
  • 嵌入式系统精确计时方案:CS2200-CP与PIC18F87J10实战
  • 嵌入式系统4键矩阵键盘多功能控制方案
  • 专业流媒体下载利器:N_m3u8DL-RE深度解析与实战指南
  • 混合精度推理的精度损失量化评估与应对策略