CVE-2017-12615源码分析与漏洞复现(Tomcat 任意文件上传)
漏洞概述
漏洞名称:Tomcat 远程代码执行漏洞(HTTP PUT 方法任意文件上传)
CVE 编号:CVE-2017-12615
CVSS 评分:8.1
影响版本:Apache Tomcat 7.0.0 - 7.0.81(含补丁绕过)
修复版本:≥ 7.0.82(官方完全修复)
漏洞类型:远程代码执行(RCE)
根本原因:当 Tomcat 启用 HTTP PUT 方法(readonly=false
)时,攻击者可构造特殊后缀文件名绕过安全校验,上传恶意 JSP 文件执行任意代码。
漏洞原理与源码分析
1. 漏洞触发条件
- 配置要求:在
conf/web.xml
中显式配置readonly=false
(默认无此配置,需手动添加):<init-param><param-name>readonly</param-name><param-value>false</param-value> </init-param>
- 系统限制:主要影响 Windows 环境(利用文件系统特性),但部分绕过方法(如
evil.jsp/
)可影响 Linux。
2. 关键源码定位
(1)请求路由入口:DefaultServlet#doPut
代码路径:org.apache.catalina.servlets.DefaultServlet
@Overrideprotected void doPut(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {if (readOnly) {// 若 readOnly=true 则拒绝请求resp.sendError(HttpServletResponse.SC_FORBIDDEN);return;}String path = getRelativePath(req);// 获取请求路径boolean exists = true;try {resources.lookup(path);// 获取资源对象} catch (NamingException e) {exists = false;}boolean result = true;File contentFile = null;Range range = parseContentRange(req, resp);InputStream resourceInputStream = null;if (range != null) {contentFile = executePartialPut(req, range, path);resourceInputStream = new FileInputStream(contentFile);} else {resourceInputStream = req.getInputStream();}try {Resource newResource = new Resource(resourceInputStream);if (exists) {resources.rebind(path, newResource);} else {resources.bind(path, newResource);// 写入文件(漏洞触发点)}} catch(NamingException e) {result = false;}...
@Overridepublic void bind(Name name, Object obj, Attributes attrs)throws NamingException {dirContext.bind(parseName(name), obj, attrs);cacheUnload(name.toString());}
漏洞点:readOnly=false
时允许文件上传,但未校验文件后缀合法性。
(2)文件写入逻辑:FileDirContext#rebind
代码路径:org.apache.catalina.fileuploads.FileDirContext
@Overridepublic void bind(String name, Object obj, Attributes attrs)throws NamingException {File file = new File(base, name);if (file.exists())throw new NameAlreadyBoundException(sm.getString("resources.alreadyBound", name));rebind(name, obj, attrs);}
public void bind(String name, Object obj) {File file = new File(base, name); // 关键:构造文件路径...// 写入文件内容(无后缀过滤)try (FileOutputStream fos = new FileOutputStream(file)) {IOUtils.copy((Resource) obj, fos);}...
}
问题:File
类处理路径时触发 系统级文件名规范化,导致绕过:
- Windows 特性:
evil.jsp::$DATA
→ 规范化后为evil.jsp
- Java 路径处理:
evil.jsp/
→ 通过File.normalize()
删除末尾/
(见java.io.File#normalize
)。
(3)安全校验绕过机制
Tomcat 通过 web.xml
路由请求:
<servlet-mapping><servlet-name>jsp</servlet-name> <!-- 处理 .jsp 动态请求 --><url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping><servlet-name>default</servlet-name> <!-- 处理静态文件/PUT请求 --><url-pattern>/</url-pattern>
</servlet-mapping>
绕过原理:
- 当请求路径为
evil.jsp/
时,不匹配*.jsp
规则 → 交由DefaultServlet
处理。 DefaultServlet
处理 PUT 请求写入文件,而规范化后的路径变为evil.jsp
→ 最终生成可执行的 JSP 文件。
漏洞复现
1. 攻击流程
2. 三种绕过方法
方法 | Payload 示例 | 利用系统特性 |
---|---|---|
空格绕过 | PUT /evil.jsp%20 HTTP/1.1 | Windows 删除文件名末尾空格 |
NTFS 数据流 | PUT /evil.jsp::$DATA HTTP/1.1 | NTFS 文件流特性 |
路径规范化 | PUT /evil.jsp/ HTTP/1.1 | File.normalize() 删除末尾 / |
3. 利用效果
- 使用 Vulhub 环境启动漏洞靶机
docker-compose build
docker-compose up -d
-
访问 http://target:8080,确认服务正常运行
-
使用冰蝎点击传输协议生成
webshell
- 上传
webshell
- 冰蝎右键新增
shell
,输入上传的url
,选择刚才上传的脚本语言以及对应的传输协议,最后保存即可
- 获取到
shell
影响范围与修复方案
1. 受影响版本
Tomcat 分支 | 受影响版本 | 安全版本 |
---|---|---|
7.0.x | 7.0.0 - 7.0.81 | ≥ 7.0.82 |
2. 官方修复方案
- 补丁提交:修订记录
- 修复逻辑:在
DefaultServlet
中强制校验文件后缀:protected boolean isPutAllowed(String path) { if (path.endsWith(".jsp") || path.endsWith(".jspx")) { return false; // 禁止上传 JSP/JSPX 文件 } return true; }
3. 临时缓解措施
- 禁用 PUT 方法:
<!-- conf/web.xml --> <init-param> <param-name>readonly</param-name> <param-value>true</param-value> <!-- 恢复默认值 --> </init-param>
- 网络层拦截(Nginx 示例):
location / { if ($request_method ~* "PUT") { return 403; } }
- 删除危险配置:检查
conf/web.xml
并移除自定义readonly
参数。
漏洞启示:
- 默认安全:生产环境应保持
readonly=true
(默认配置),避免开启高危功能。 - 纵深防御:结合代码补丁、网络层过滤(WAF)和文件监控(如审计
webapps/*.jsp
)。 - 跨平台风险:即使漏洞主要影响 Windows,Linux 系统也可能因路径规范化特性被绕过。
参考链接
- CVE-2017-12615 官方通告(Apache Tomcat)
- 漏洞原理深度分析(阿里云先知社区)
- 补丁绕过技术解析(嘶吼)
- 漏洞复现指南(Vulhub)