CVE-2026-34486 — Apache Tomcat Tribes EncryptInterceptor 失效开放型远程代码执行¶
发布于 2026-06-04
严重:未经身份验证的远程代码执行
能够访问 Tribes 集群接收端口(TCP)的攻击者,无需任何凭据或会话,即可在 Tomcat JVM 内部执行任意代码。
| 字段 | 详情 |
|---|---|
| 项目 | Apache Tomcat |
| 受影响组件 | org.apache.tomcat:tomcat-tribes(集群) |
| 严重级别 | HIGH |
| CVSS 3.1 | 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CWE | CWE-311(敏感数据缺少加密) |
| 受影响版本 | 9.0.116、10.1.53、11.0.20(仅这三个单点版本) |
| 已修复版本 | 9.0.117、10.1.54、11.0.21 |
| GHSA | GHSA-69r9-qgr7-g2wj |
| 影响 | 未经身份验证的远程代码执行 |
1. 漏洞概述¶
Apache Tomcat 内置了名为 Tribes 的集群子系统,用于在多个节点之间同步会话状态。启用集群流量加密时,拦截器类 EncryptInterceptor 在将入站消息传递到后续处理管道之前负责解密。针对早期 CVE(CVE-2026-29146)的一次修复重构提交意外引入了"失效开放"条件:若解密过程抛出异常,原始未解密字节仍会被转发。下游管道最终会在没有类过滤的情况下调用 ObjectInputStream.readObject(),因此能够访问 Tribes 接收端口(默认 TCP 4000,无需身份验证)的攻击者,可发送构造好的 Java 序列化 Gadget 链,在 Tomcat JVM 内执行任意代码。
根本原因¶
文件: java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
方法: messageReceived(ChannelMessage msg)
问题提交(Tomcat 11 对应 6d955cc,Tomcat 9/10 对应 607ebc0,标题均为"Add support for new algorithms provided by JPA providers")将 super.messageReceived(msg) 的调用从 try 块内部移到了 catch 块之后。decrypt() 抛出的异常被捕获并记录日志,但执行流程继续进入 super.messageReceived(msg),将原始未修改的字节转发出去。
漏洞代码(9.0.116 / 10.1.53 / 11.0.20):
public void messageReceived(ChannelMessage msg) {
try {
byte[] data = msg.getMessage().getBytes();
data = encryptionManager.decrypt(data); // throws GeneralSecurityException on bad input
XByteBuffer xbb = msg.getMessage();
xbb.clear();
xbb.append(data, 0, data.length);
// super.messageReceived() is NOT here
} catch (GeneralSecurityException gse) {
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
// exception swallowed — execution continues
}
super.messageReceived(msg); // BUG: raw bytes forwarded regardless of decrypt outcome
}
修复提交(Tomcat 11 对应 1fab40cc,Tomcat 10 对应 55f3eb91,Tomcat 9 对应 776e12b3)将 super.messageReceived(msg) 移回 try 块内部,GeneralSecurityException 将导致消息被静默丢弃而非转发:
@@ -140,10 +140,10 @@ public void messageReceived(ChannelMessage msg) {
xbb.clear();
xbb.append(data, 0, data.length);
+ super.messageReceived(msg);
} catch (GeneralSecurityException gse) {
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
}
- super.messageReceived(msg);
修复生效后,解密失败会在 catch 块内终止消息处理,super.messageReceived() 不再被调用。未修复时,super.messageReceived() 最终会调用 XByteBuffer.deserialize(),该方法使用 ObjectInputStream.readObject() 且不进行类过滤,攻击者构造的含 Gadget 链(本次复现使用 CommonsCollections6)的字节序列会触发任意命令执行。
2. 漏洞复现环境¶
复现环境由单个 Docker 容器组成,使用官方镜像 tomcat:11.0.20-jdk17-temurin-jammy,并配置了包含漏洞 EncryptInterceptor 的 Tribes 集群通道。无需第二个集群节点:失效开放路径存在于接收方的入站处理逻辑中,单个节点即可在接收端口上处理任意到达的帧。
关键配置说明:
- Tomcat 11.0.20 被精确固定。失效开放的
messageReceived()仅存在于 9.0.116、10.1.53 和 11.0.20 这三个单点版本中,早期版本不受此漏洞影响(但可能存在早期的 CVE-2026-29146 问题)。 - Commons Collections 3.2.1 被放置在 Tomcat 服务器类路径
/usr/local/tomcat/lib/commons-collections-3.2.1.jar(在 Dockerfile 中通过 SHA1 对 Maven Central 进行了校验),是 CommonsCollections 反序列化链可达的 Gadget 来源。 EncryptInterceptor在server.xml中配置了静态 128 位 AES 密钥(cafebabecafebabecafebabecafebabe)。填充密码变体(AES/CBC/PKCS5Padding)使攻击者发送的未加密字节触发IllegalBlockSizeException,即暴露失效开放路径的那个异常。- Tribes NioReceiver 在容器内绑定
0.0.0.0:4000,以127.0.0.1:4000暴露给宿主机,无任何身份验证。 - JVM 通过
gosu以非特权的tomcat服务用户(UID 999)运行。被劫持控制流创建的文件将归tomcat所有,验证步骤会检查这一身份。 - 镜像构建时会创建归
tomcat用户所有的/marker目录;入口点脚本在每次容器启动时清除所有陈旧的标记文件,确保每次运行都有干净的基线。
拓扑结构:
| 名称 | 角色 | 网络 | 端口 |
|---|---|---|---|
cve-2026-34486-tomcat |
漏洞版本 Tomcat 11.0.20 节点 | env_default 桥接网络 |
127.0.0.1:4000(Tribes 接收端),127.0.0.1:8080(HTTP) |
启动环境:
下载环境文件 — env.zip — 或单独引用各文件:env/Dockerfile、env/docker-compose.yml、env/config/server.xml、env/config/entrypoint.sh。
等待容器变为健康状态(健康检查会测试 Tribes 接收端套接字是否接受连接),然后确认环境版本正确:
# 1. 确认接收端可达
nc -z -w 3 127.0.0.1 4000 && echo "receiver up"
# 2. 确认确切的漏洞版本
docker exec cve-2026-34486-tomcat /usr/local/tomcat/bin/version.sh | grep "Server number"
# 预期输出: Server number: 11.0.20.0
# 3. 确认 EncryptInterceptor 已加载且接收端已绑定
docker logs cve-2026-34486-tomcat 2>&1 | grep -E "EncryptInterceptor|Receiver Server Socket bound"
# 4. 确认 Gadget 在类路径上
docker exec cve-2026-34486-tomcat ls /usr/local/tomcat/lib/commons-collections-3.2.1.jar
# 5. 确认标记基线为空,JVM 以服务用户身份运行
docker exec cve-2026-34486-tomcat bash -c 'ls -A /marker; ps -o user= -C java'
若要验证已打补丁的版本不受影响,可将基础镜像替换为 tomcat:11.0.21-jdk17-temurin-jammy,使用相同的 server.xml 重新构建,确认发送漏洞利用载荷后不会产生任何标记文件。
3. 漏洞利用过程¶
漏洞利用由三个 Python/Bash 脚本实现,无需 Java 工具链或 ysoserial Jar 包。下载利用文件 — exploit.zip — 或单独引用:exploit/run.sh、exploit/gadget.py、exploit/send.py。
第一步 — 确认接收端可达且版本为漏洞版本¶
nc -z -w 3 127.0.0.1 4000 && echo "receiver up" && \
docker exec cve-2026-34486-tomcat /usr/local/tomcat/bin/version.sh | grep "Server number"
预期输出:receiver up 及 Server number: 11.0.20.0。
第二步 — 执行漏洞利用¶
编排脚本 exploit/run.sh 接受目标主机、端口和标记路径三个参数。标记路径应嵌入新鲜的随机数(nonce),以便将生成的文件归属于本次运行:
run.sh 内部执行两个子步骤:
2a. 构建 CommonsCollections6 Gadget(纯 Python,无需 Java):
gadget.py 用 Python 构造 Java 序列化流,使用直接从 Commons Collections 3.2.1 和 JDK 17 运行时读取的正确 serialVersionUID 值和字段布局。调用链为:HashMap.readObject() → TiedMapEntry.hashCode() → LazyMap.get() → ChainedTransformer → ConstantTransformer(Runtime.class) → InvokerTransformer("getMethod") → InvokerTransformer("invoke") → InvokerTransformer("exec", cmdArray)。
2b. 将原始载荷封装为 Tribes 帧并以未加密方式发送:
send.py 将序列化字节封装在 Tomcat Tribes 线协议信封(FLT2002 … TLF2003)中,包含有效的 ChannelData 结构和结构正确的 MemberImpl 数据块,然后通过普通 TCP 套接字将整个帧发送到接收端,不进行任何加密。在服务端,EncryptInterceptor.messageReceived() 尝试 AES 解密,收到 javax.crypto.IllegalBlockSizeException(输入长度不是填充密码所需的 16 的倍数),将其作为 GeneralSecurityException 记录日志,然后由于定位错误的 bug,使用原始未修改的字节调用 super.messageReceived(msg)。这些字节到达 XByteBuffer.deserialize() → ObjectInputStream.readObject(),CommonsCollections6 链触发,Tomcat JVM 以 tomcat 服务用户身份执行 touch /marker/pwned-<NONCE>。
第三步 — 通过独立渠道观察证据¶
标记文件通过特权 docker exec 调用进行验证,该调用完全独立于漏洞利用脚本本身的输出,使用 root 权限直接检查容器文件系统:
已验证复现运行中的实际观测结果(nonce 为 fb8f88e5b044e839):
docker exec cve-2026-34486-tomcat ls -l /marker/pwned-fb8f88e5b044e839
-rw-r----- 1 tomcat tomcat 0 Jun 3 01:35 /marker/pwned-fb8f88e5b044e839
文件归属者为 tomcat(UID 999),即 JVM 内部的非特权服务用户,而非 root,也非运行漏洞利用脚本的宿主机。文件名中嵌入的 nonce 排除了预先放置或偶然创建的文件。漏洞利用脚本中不含任何 docker exec、open() 或针对标记文件的 touch 操作;该文件只能由受害 JVM 内部执行的代码创建。
验证日志帧将此结果归属于 EncryptInterceptor.messageReceived 的失效开放路径:
实际观测到的日志输出:
SEVERE [Tribes-Task-Receiver[Catalina-Channel]-3]
org.apache.catalina.tribes.group.interceptors.EncryptInterceptor.messageReceived
Failed to decrypt message
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at org.apache.catalina.tribes.group.interceptors.EncryptInterceptor.messageReceived(EncryptInterceptor.java:135)
日志中恰好出现了一条"Failed to decrypt message"记录,与单次漏洞利用运行对应。EncryptInterceptor.java:135 处的堆栈帧正是补丁移除的、位于 try 块外部的 super.messageReceived(msg) 调用点。
环境清理¶
完成后,停止并删除容器及其数据卷:
4. 安全建议¶
修复方案¶
升级到已修复的版本:
- Tomcat 11: 升级至 11.0.21 或更高版本。
- Tomcat 10: 升级至 10.1.54 或更高版本。
- Tomcat 9: 升级至 9.0.117 或更高版本。
修复方案是对问题重构提交的部分回滚:将 super.messageReceived(msg) 移回 try 块内部,使解密过程中的 GeneralSecurityException 导致消息被静默丢弃(失效关闭),不再到达反序列化入口点。
仅影响特定版本
只有 9.0.116、10.1.53 和 11.0.20 这三个确切的单点版本受此特定漏洞影响。这些版本之前的版本不受 CVE-2026-34486 影响(但可能存在 CVE-2026-29146,即触发本次重构的早期填充预言机问题)。
缓解措施与临时方案¶
如果无法立即升级:
- 禁用
EncryptInterceptor。 失效开放路径仅在配置了EncryptInterceptor时触发。使用 Tribes 集群但未配置EncryptInterceptor的部署不会通过此路径暴露反序列化入口。从server.xml中移除该拦截器可消除此漏洞,但同时也会移除集群流量加密,需权衡取舍。 - 通过防火墙限制接收端口访问。 将 Tribes 接收端口(默认 TCP 4000)的访问限制为受信任的集群对等节点。此漏洞仅对能够直接建立 TCP 连接到此端口的主机可利用。若集群对等节点均位于隔离的私有网络段,边缘防火墙可显著降低攻击面。
- 从类路径中移除 Gadget 链。 在没有类过滤的情况下,
ObjectInputStream.readObject()需要服务器类路径上存在可用的 Gadget 链。从类路径中移除 Commons Collections 及其他可利用的 Gadget 库会降低 RCE 的可行性,但这不是可靠的缓解措施,底层失效开放 bug 仍然存在,且可能存在其他 Gadget 链。
检测方法¶
一次成功的攻击在命令静默执行前,服务端日志中会留下以下痕迹:
SEVERE [Tribes-Task-Receiver[Catalina-Channel]-...]
org.apache.catalina.tribes.group.interceptors.EncryptInterceptor.messageReceived
Failed to decrypt message
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
readObject 不会记录任何异常,命令执行是静默的。建议监控此日志模式,并与 Tomcat 工作目录下的异常文件创建事件进行关联分析。
参考资料¶
- NVD CVE-2026-34486 — 漏洞标识、CWE-311、CVSS 7.5
- GHSA-69r9-qgr7-g2wj — 受影响/已修复版本、包名
- Apache 安全公告 — Tomcat 11 — 严重级别"Important",披露时间线
- Apache 安全公告 — Tomcat 10 — 10.1.54 修复详情
- Apache 安全公告 — Tomcat 9 — 9.0.117 修复详情
- 补丁提交 1fab40cc(Tomcat 11) — 对 6d955cc 的部分回滚
- 补丁提交 55f3eb91(Tomcat 10) — 对 607ebc0 的部分回滚
- 补丁提交 776e12b3(Tomcat 9) — 对 607ebc0 的部分回滚
- Apache 邮件列表公告 — 最初公开披露
- striga-ai/CVE-2026-34486 — 基于 Docker 的端到端 PoC,含 CC6 Gadget 和 Tribes 帧构建器
- 404-src/CVE-2026-34486 — 含 ysoserial 和交互式 Shell 模式的 Python
exp.py - AirSkye/CVE-2026-34486-poc — 字节码级分析与中文分析报告
- herodevs 漏洞目录 — 补充说明