SRC 挖洞:Apache Tomcat 加密拦截器绕过深度复盘,CVE-2026-34486 fail-open 一行代码怎么打穿集群通信

作者 akihi(白帽攻防录讲师),某甲方网络安全工程师,合合 SRC 年度第一、腾讯 SRC 连续三年前十,单漏洞赏金 4w+,擅长 Web/App/PC 客户端漏洞挖掘。专注中间件安全、反序列化漏洞、代码审计。本文基于公开 CVE 信息与技术原理深度复盘,供安全研究与防护参考。

一、漏洞时间线

2026 年 4 月,Apache Tomcat 安全团队披露了一个 Critical 级别的漏洞 CVE-2026-34486。这个漏洞非常特殊------它不是一个新发现的漏洞,而是由之前修复另一个漏洞时引入的回归缺陷。一行代码的位置变化,让整个集群加密机制变成了 fail-open 的安全隐患。

时间 事件 影响版本
2026-03 修复 CVE-2026-29146(padding oracle) ------
2026-04-09 NVD 发布 CVE-2026-34486 详情 10.1.x / 11.0.x
2026-04-21 安全团队发布技术分析 ------
2026-04-28 FreeBuf 发布中文分析 ------
2026-08-04 CISA 将其加入 KEV 已知被利用漏洞清单 ------
2026-08-31 安全厂商发布高优先级预警 ------

这个漏洞的 CVSS 评分为 9.8(Critical),攻击复杂度低,无需认证,无需用户交互,是典型的"一键利用"漏洞。CISA 在 8 月 4 日将其列入 KEV 清单,确认已被实际利用。

二、攻击链全景

让我们先用一张流程图还原完整的攻击路径:

复制代码
攻击者发现暴露在公网的 Tomcat 集群节点
        │
        ▼
第一步:发现 Tribes 集群通信端口
        │  Tomcat Tribes 默认使用 TCP 4000 端口
        │  用于集群节点之间的消息同步
        │  正常情况下消息应该加密
        ▼
第二步:分析加密机制
        │  Tomcat 使用 EncryptInterceptor 加密集群消息
        │  正常流程:加密消息 → 传输 → 解密 → 处理
        │  漏洞版本:解密失败 → 消息仍然继续处理
        ▼
第三步:构造恶意序列化消息
        │  攻击者不需要加密
        │  直接发送恶意 Java 序列化对象
        │  内容是精心构造的反序列化 payload
        ▼
第四步:发送到 TCP 4000 端口
        │  原始字节流直接发送到 NioReceiver
        │  EncryptInterceptor 尝试解密
        │  解密失败,抛出 GeneralSecurityException
        ▼
第五步:fail-open 发生
        │  catch 块记录了错误日志
        │  但是!super.messageReceived(msg) 在 try 块外面
        │  消息没有被丢弃,而是继续传递下去
        ▼
第六步:到达反序列化层
        │  原始的恶意字节流进入 Tribes 消息处理
        │  Java 反序列化执行
        │  payload 中的恶意代码被执行
        ▼
攻击者获得远程代码执行权限
        │
        ├─ 在 Tomcat 服务器上执行任意命令
        ├─ 读取/修改服务器上的文件
        ├─ 横向移动到内网其他节点
        └─ 完全控制整个集群

关键结论: 这个漏洞的核心是"一行代码的位置"------super.messageReceived(msg) 从 try 块里面移到了外面。从编译器的角度看代码完全正确,但从安全的角度看,这就是从 fail-close 变成了 fail-open。解密失败不再丢弃消息,而是让原始字节流直接通过。

三、Tomcat Tribes 原理

3.1 什么是 Tomcat Tribes

Apache Tomcat Tribes 是 Tomcat 的集群通信组件,用于在多个 Tomcat 节点之间同步会话、配置和消息:

组件 作用
NioReceiver 监听 TCP 端口(默认 4000),接收集群消息
EncryptInterceptor 加密/解密集群消息,防止窃听和篡改
DispatchChannel 消息分发通道,将消息分发给各个拦截器
Deserialization 将接收到的字节流反序列化为 Java 对象

3.2 正常的加密流程

正常情况下,集群消息的处理流程:

  1. 发送端:用密钥加密消息内容
  2. 网络传输:消息是密文,即使被窃听也无法解读
  3. 接收端:EncryptInterceptor 尝试解密
  4. 解密成功:消息继续传递给下一个拦截器
  5. 解密失败:丢弃消息,记录错误日志
  6. 反序列化:只有解密成功的消息才会被反序列化

3.3 为什么需要加密

集群通信消息中包含敏感信息:

复制代码
// 集群消息中可能包含的数据
// - 用户会话数据(Session)
// - 配置信息
// - 应用状态
// - 节点之间的认证凭证

// 如果不加密:
// 1. 攻击者可以窃听集群通信
// 2. 攻击者可以伪造集群消息
// 3. 攻击者可以直接发送恶意序列化对象
// 4. 导致远程代码执行

四、漏洞深度分析

4.1 漏洞根因:一行代码的位置

根据安全团队的分析,漏洞的根本原因是修复 CVE-2026-29146 时引入的回归:

复制代码
// 修复前(正确的代码)
@Override
public void messageReceived(ChannelMessage msg) {
    try {
        // 尝试解密消息
        byte[] decrypted = decrypt(msg.getMessage());
        msg.getMessage().transferFrom(decrypted);
        
        // 只有解密成功才继续处理
        super.messageReceived(msg);
    } catch (GeneralSecurityException gse) {
        // 解密失败,丢弃消息
        log.error("Failed to decrypt message", gse);
        // 消息被丢弃,不继续处理
    }
}

// 漏洞版本(错误的代码)
@Override
public void messageReceived(ChannelMessage msg) {
    try {
        // 尝试解密消息
        byte[] decrypted = decrypt(msg.getMessage());
        msg.getMessage().transferFrom(decrypted);
    } catch (GeneralSecurityException gse) {
        // 解密失败,只记录日志
        log.error("Failed to decrypt message", gse);
        // 注意!super.messageReceived 不在 try 块里面了!
    }
    
    // 这行代码被移到了 try-catch 外面
    // 无论解密成功还是失败,都会执行
    super.messageReceived(msg);
}

// 区别只有一个:
// super.messageReceived(msg) 从 try 块里面移到了外面
// 从编译器看,代码完全正确
// 但从安全看,这就是 fail-open

4.2 为什么会引入这个回归

这个回归是在修复另一个漏洞 CVE-2026-29146 时引入的:

漏洞 问题 修复方式
CVE-2026-29146 CBC 模式存在 padding oracle 改进解密逻辑
CVE-2026-34486 修复时把 super.messageReceived 移到了外面 把这行代码移回 try 块里面

这是一个典型的"修复引入新漏洞"的案例。开发者本意是好的------修复 padding oracle 漏洞,但在修改代码时不小心改变了控制流,导致了更严重的安全问题。

4.3 攻击利用示例

攻击者如何利用这个漏洞执行代码:

复制代码
// 攻击步骤

// 1. 攻击者发现 Tomcat 集群的 TCP 4000 端口
nmap -sV target.com -p 4000

// 2. 构造恶意 Java 序列化 payload
// 比如使用 CommonsCollections 链
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(evilPayload);

// 3. 直接发送原始字节流
// 不需要加密,EncryptInterceptor 会尝试解密
// 解密失败,但消息仍然继续处理
Socket s = new Socket("target.com", 4000);
OutputStream os = s.getOutputStream();
os.write(serializedPayload);
os.flush();

// 4. Tomcat 接收到消息
// EncryptInterceptor.messageReceived() 被调用
// decrypt() 抛出 GeneralSecurityException
// catch 块记录错误
// super.messageReceived(msg) 仍然执行
// 消息到达反序列化层
// 恶意代码执行

// 实际影响:
// - 远程代码执行(RCE)
// - 读取服务器文件
// - 修改应用配置
// - 横向移动

4.4 影响范围

产品 影响版本 影响程度
Apache Tomcat 10.1.x 所有启用集群加密的版本 未授权 RCE
Apache Tomcat 11.0.x 所有启用集群加密的版本 未授权 RCE
利用端口 TCP 4000(Tribes 集群通信) ------
利用条件 启用了集群和加密拦截器 ------
CISA KEV 2026-08-04 列入 确认被利用

五、修复方案分析

Apache Tomcat 在后续版本中修复了这个问题:

修复项 修复方式
代码位置 把 super.messageReceived(msg) 移回 try 块里面
异常处理 解密失败时丢弃消息,不继续处理
安全默认 fail-close:失败时拒绝,而不是放行

5.1 安全编码原则

复制代码
// 安全编码的 fail-close 原则

// 不好的做法(fail-open)
try {
    decrypt(data);
} catch (Exception e) {
    log.error("decrypt failed", e);
}
// 即使失败也继续处理
process(data);

// 好的做法(fail-close)
try {
    decrypt(data);
    process(data);  // 只有成功才处理
} catch (Exception e) {
    log.error("decrypt failed, dropping message", e);
    // 失败时丢弃消息,不继续处理
    return;
}

// 原则:
// 安全相关的操作,失败时应该拒绝
// 而不是放行
// 这就是 fail-close vs fail-open

5.2 Tomcat 加固措施

复制代码
// Tomcat 安全加固

// 1. 及时更新
// 升级到修复了 CVE-2026-34486 的版本

// 2. 限制集群端口访问
// 不要让 TCP 4000 暴露在公网
// 只允许集群节点之间通信
iptables -A INPUT -p tcp --dport 4000 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 4000 -j DROP

// 3. 禁用不必要的集群功能
// 如果不需要集群,就不要启用

// 4. 监控异常流量
// 监控 TCP 4000 端口的连接
// 特别是来自公网的连接

// 5. 网络分段
// 集群通信放在内网
// 不要和公网直接通信

六、SRC 审计启示录

6.1 中间件安全审计清单

在 SRC 挖洞过程中,针对中间件的审计清单:

# 检查项 检测方法
1 是否有暴露的管理端口 nmap 扫描常见端口
2 集群端口是否暴露公网 检查 4000、8009 等端口
3 是否启用了不安全的反序列化 发送序列化测试 payload
4 加密机制是否正确实现 检查 fail-open / fail-close
5 是否有已知 CVE 未修复 对比版本号和 CVE 列表

6.2 常见中间件漏洞

复制代码
// 常见中间件漏洞

// 1. 反序列化漏洞
// WebLogic、WebSphere、JBoss、Tomcat
// 发送恶意序列化对象执行代码

// 2. 管理接口未授权
// Tomcat Manager、JMX Console
// 未授权访问部署 WAR 包

// 3. 集群通信漏洞
// 就是本次漏洞的类型
// 集群消息没有正确验证

// 4. 信息泄露
// 错误页面泄露版本信息
// 帮助攻击者精确匹配 CVE

6.3 fail-open vs fail-close

场景 fail-open 风险 fail-close 正确做法
解密失败 原始数据直接通过 丢弃消息,拒绝处理
认证失败 允许访问 返回 403
权限检查异常 默认允许 默认拒绝
输入验证异常 接受输入 拒绝输入

七、防护建议

  1. 及时更新:升级到修复了 CVE-2026-34486 的 Tomcat 版本
  2. 端口限制:TCP 4000 集群端口不要暴露公网,只允许内网访问
  3. 最小化服务:不需要集群功能就不要启用
  4. 监控告警:监控集群端口的异常连接和流量
  5. 网络分段:集群通信放在安全内网段
  6. 漏洞扫描:定期扫描服务器上的中间件版本和已知漏洞

八、总结

CVE-2026-34486 是一个非常有教育意义的漏洞------它告诉我们:安全是关于控制流的。一行代码的位置变化,就可能从 fail-close 变成 fail-open,从安全变成不安全。在 SRC 挖洞实践中,"补丁回归"是一个高价值的审计方向:看看最近修复的漏洞,修复代码是否引入了新的问题?特别是异常处理、控制流这些细节,往往藏着最严重的安全隐患。


相关推荐
写了20年代码的老程序员1 小时前
想让 AI 改 Bug 快准狠?先给日志加个业务代码坐标
java·后端·apache log4j
SL_staff1 小时前
JVS-Rules vs Drools:金融风控团队为何转向业务可编辑的决策平台
java·spring cloud·github
Zhou1411361 小时前
SpringMVC_03_进阶功能
java
咖啡八杯1 小时前
常量与枚举设计规范:HttpStatus 自定义 601 警告码
java·架构·代码规范
ThinkerQAQ_1 小时前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·picasso
小白的码BUG之路1 小时前
Docker -- 基本命令
java·docker·eureka
后端LV1 小时前
把限流从「注解」做活:RateLimitKeyResolver 四种真实业务的键玩法
java·后端
imDwAaY1 小时前
Bean的生命周期
java·笔记·后端·学习·spring·dubbo
不停喝水1 小时前
【前端转全栈java速通课】 项目实战④-1 Spring Boot 创建项目-定义接口-请求参数处理-分层规范-依赖注入
java·前端·spring boot