【Java中间件】CVE-2026-34486:Tomcat EncryptInterceptor 补丁绕过修复指南(会话复制"假加密"危机)
摘要: 本文复盘一次电商交易系统 Tomcat 集群遭遇 CVE-2026-34486(敏感数据加密缺失,NVD 初步评分 9.8)的处置全过程。该漏洞源于官方对 CVE-2026-29146 的修复不完整,导致 EncryptInterceptor 加密机制仍可被绕过------本以为是"打了补丁就安全"的会话复制加密,实际仍在部分消息路径上明文传输。文章从一次例行抓包巡检切入,剖析 Tomcat Tribes 集群通道与拦截器栈的加密原理,给出升级 11.0.21/10.1.54/9.0.117、网络层隔离、外置会话存储三层修复方案,并配套滚动升级脚本、Prometheus 告警规则与抓包复核清单。结合 NVD 9.8 与厂商 7.5 的评分分歧分析,帮助运维团队理解"补丁绕过"类漏洞的排期逻辑,6 节点集群年度净收益约 9.1 万元、ROI 约 18.6 倍。
引言:一次例行抓包巡检引发的"假加密"惊魂
时间 :2026 年 10 月 15 日 22:47
告警来源 :季度例行安全巡检 + 抓包复核脚本
业务影响 :电商交易系统 6 节点 Tomcat 集群,会话复制流量疑似明文
紧急程度:P1(数据泄露风险,非直接 RCE)
10 月中旬,我们按季度对核心交易链路做例行安全巡检。其中一项是"集群内部流量加密有效性验证"------对 Tomcat 集群节点间的会话复制流量做抓包抽查。
抓包结果出乎意料:在复制端口上,除了正常的密文块,还夹杂着肉眼可读的会话片段------明文里能看到序列化后的 SessionID、用户登录态字段。而我们的集群配置里,EncryptInterceptor 是明确开启的,密钥半年前就配好了。
第一反应是"抓包脚本抓错口了"。排查了半小时,确认抓的是节点间复制流量无疑。当晚翻 Apache 官方安全公告才对上号:CVE-2026-34486------官方承认此前对 CVE-2026-29146 的修复不完整,EncryptInterceptor 仍存在绕过路径,部分会话复制消息会以明文发送。
更扎心的是版本对照:我们的 10.1.53,恰好就是官方为了修 CVE-2026-29146 而发布的版本------为了修上次的"加密缺失"而打的补丁,自己又被打穿了。这也是本专栏继 006 篇 Apache MINA 之后又一次"补丁绕过"主题的实战复盘。
本文完整记录这次事件的处置过程:如何确认影响面、如何在"评分分歧"(NVD 9.8 vs 厂商 7.5)中做排期决策、以及 6 节点集群如何在不中断交易的情况下完成滚动升级。
一、漏洞背景与影响面盘点
1.1 CVE-2026-34486 基础信息
| 项目 | 内容 |
|---|---|
| CVE 编号 | CVE-2026-34486 |
| 漏洞归类 | 敏感数据加密缺失(Missing Encryption of Sensitive Data,CWE-311 类) |
| 本质 | CVE-2026-29146 的修复不完整,EncryptInterceptor 加密机制仍可被绕过 |
| 受影响版本 | Tomcat 11.0.20 / 10.1.53 / 9.0.116 |
| 修复版本 | Tomcat 11.0.21 / 10.1.54 / 9.0.117 |
| 利用前置条件 | 集群模式运行 + 启用 EncryptInterceptor + 攻击者具备链路流量观测能力(链路中间人/镜像/被攻陷节点) |
| 影响后果 | 会话复制流量中的部分消息明文可读,可还原 Session 内容(登录态、SessionID、Session 内用户数据) |
| 官方公告 | Apache Tomcat 官方 Security Pages 各分支安全公告 |
1.2 CVSS 评分分歧:9.8 还是 7.5?
这个漏洞在评分上出现了明显分歧,处置排期时必须理解分歧来源:
| 评分来源 | 评分 | 说明 |
|---|---|---|
| NVD(初步/preliminary) | 9.8(Critical) | 初步自动评分,通常按最坏情况向量给出,细节分析尚缺 |
| 厂商侧(如 Red Hat 生态评分) | 7.5 | 纳入利用前置条件:需集群+EncryptInterceptor 配置 + 网络位置 |
分歧的本质:NVD 初步评分在缺少深度分析时倾向按"默认暴露面"给高分;而厂商评分会考虑实际利用链条------攻击者必须让流量经过自己可控的观测点,才能还原明文会话。
我们的处置原则 :评分分歧 ≠ 风险分歧。对命中"集群 + EncryptInterceptor"配置的用户,这就是实打实的数据泄露风险;对未启用集群的部署,则基本无影响。按自身配置定风险,而不是按评分数字定排期。
另外需要说明:个别低可信度渠道提及该漏洞已进入在野利用相关目录,截至本文发布未见权威佐证。建议保持关注官方渠道,但不以单一来源作为定级依据。
1.3 漏洞危害三要素
text
⚠️ 危害维度:
1. **影响面窄但隐蔽** - 仅命中"集群模式 + EncryptInterceptor"配置
- 未启用集群的 Tomcat 部署基本不受影响
- 但命中者往往自认为"已加密、已安全",防范意识最低
2. **机密性破坏** - 会话复制流量部分明文可读
- Session 中常含登录态、用户 PII、权限上下文
- 可用于会话劫持、横向冒充、敏感数据窃取
3. **"补丁绕过"心理杀伤** - 修复过 CVE-2026-29146 的用户恰是本轮受害者
- 合规台账上"已完成修复",实际处于假加密状态
- 排期极易被"已经修过了"误导而搁置
1.4 影响版本矩阵
#mermaid-svg-NS92CxAYj6jYPUPb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NS92CxAYj6jYPUPb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NS92CxAYj6jYPUPb .error-icon{fill:#552222;}#mermaid-svg-NS92CxAYj6jYPUPb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NS92CxAYj6jYPUPb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NS92CxAYj6jYPUPb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NS92CxAYj6jYPUPb .marker.cross{stroke:#333333;}#mermaid-svg-NS92CxAYj6jYPUPb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NS92CxAYj6jYPUPb p{margin:0;}#mermaid-svg-NS92CxAYj6jYPUPb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NS92CxAYj6jYPUPb .cluster-label text{fill:#333;}#mermaid-svg-NS92CxAYj6jYPUPb .cluster-label span{color:#333;}#mermaid-svg-NS92CxAYj6jYPUPb .cluster-label span p{background-color:transparent;}#mermaid-svg-NS92CxAYj6jYPUPb .label text,#mermaid-svg-NS92CxAYj6jYPUPb span{fill:#333;color:#333;}#mermaid-svg-NS92CxAYj6jYPUPb .node rect,#mermaid-svg-NS92CxAYj6jYPUPb .node circle,#mermaid-svg-NS92CxAYj6jYPUPb .node ellipse,#mermaid-svg-NS92CxAYj6jYPUPb .node polygon,#mermaid-svg-NS92CxAYj6jYPUPb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NS92CxAYj6jYPUPb .rough-node .label text,#mermaid-svg-NS92CxAYj6jYPUPb .node .label text,#mermaid-svg-NS92CxAYj6jYPUPb .image-shape .label,#mermaid-svg-NS92CxAYj6jYPUPb .icon-shape .label{text-anchor:middle;}#mermaid-svg-NS92CxAYj6jYPUPb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NS92CxAYj6jYPUPb .rough-node .label,#mermaid-svg-NS92CxAYj6jYPUPb .node .label,#mermaid-svg-NS92CxAYj6jYPUPb .image-shape .label,#mermaid-svg-NS92CxAYj6jYPUPb .icon-shape .label{text-align:center;}#mermaid-svg-NS92CxAYj6jYPUPb .node.clickable{cursor:pointer;}#mermaid-svg-NS92CxAYj6jYPUPb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NS92CxAYj6jYPUPb .arrowheadPath{fill:#333333;}#mermaid-svg-NS92CxAYj6jYPUPb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NS92CxAYj6jYPUPb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NS92CxAYj6jYPUPb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NS92CxAYj6jYPUPb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NS92CxAYj6jYPUPb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NS92CxAYj6jYPUPb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NS92CxAYj6jYPUPb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NS92CxAYj6jYPUPb .cluster text{fill:#333;}#mermaid-svg-NS92CxAYj6jYPUPb .cluster span{color:#333;}#mermaid-svg-NS92CxAYj6jYPUPb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NS92CxAYj6jYPUPb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NS92CxAYj6jYPUPb rect.text{fill:none;stroke-width:0;}#mermaid-svg-NS92CxAYj6jYPUPb .icon-shape,#mermaid-svg-NS92CxAYj6jYPUPb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NS92CxAYj6jYPUPb .icon-shape p,#mermaid-svg-NS92CxAYj6jYPUPb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NS92CxAYj6jYPUPb .icon-shape .label rect,#mermaid-svg-NS92CxAYj6jYPUPb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NS92CxAYj6jYPUPb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NS92CxAYj6jYPUPb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NS92CxAYj6jYPUPb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Tomcat 版本分布
9.0.x 分支
10.1.x 分支
11.0.x 分支
9.0.116
受影响: 含不完整修复 ❌
9.0.117+
完整修复 ✅
10.1.53
受影响: 含不完整修复 ❌
10.1.54+
完整修复 ✅
11.0.20
受影响: 含不完整修复 ❌
11.0.21+
完整修复 ✅
更早版本(早于 29146 修复基线)
未引入本次绕过
但需处理 29146 本身 ⚠
升级至 9.0.117
升级至 10.1.54
升级至 11.0.21
直接升级至各分支
最新修复版本
版本关系说明:受影响版本列表与 CVE-2026-29146 的修复版本完全重合,印证了"上次修复引入本次绕过"的演进关系。更早版本未引入本次绕过问题,但若早于 29146 修复基线,本身仍需处理 29146,最稳妥的做法是直接升到各分支最新修复版本。
二、漏洞原理深度剖析
2.1 Tomcat 集群会话复制架构
Tomcat 通过 SimpleTcpCluster + DeltaManager(全量复制到所有节点)或 BackupManager(主备式)实现会话复制。其底层通信由 Tribes 通道完成:
text
会话复制链路(DeltaManager 模式):
用户请求 → 节点A 修改 Session
→ DeltaManager 生成增量消息(serialize 会话变更)
→ Tribes Channel
→ 拦截器栈(TcpFailureDetector / MessageDispatchInterceptor / EncryptInterceptor ...)
→ Receiver/Sender(TCP 单播数据端口,默认 4000;组播成员管理默认 228.0.0.4:45564)
→ 节点B 接收增量 → 反序列化 → 应用到本地 Session
关键点:会话数据在节点间以序列化字节流传输,内容就是 Session 的完整快照。这条链路一旦明文,等于把全站登录态在网线上裸奔。
2.2 EncryptInterceptor 的定位
EncryptInterceptor 是 Tomcat 官方提供的通道级加密拦截器,配置在 server.xml 的 Cluster/Channel 拦截器栈中:
xml
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!-- 官方要求 EncryptInterceptor 放在拦截器栈最前部,
以保证进入通道的消息先经过加密处理 -->
<Interceptor className="org.apache.catalina.tribes.group.interceptors.EncryptInterceptor"
encryptionKey="REDACTED-HEX-KEY"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<!-- Valve / Deployer 等省略 -->
</Cluster>
设计意图很清晰:即便复制流量被迫穿越不可信网段(跨机房专线、云上 VPC 对等连接),密文也能兜底。密钥配置与算法细节以所用版本的官方文档为准。
2.3 补丁为何被绕过:不完整修复的连锁反应
这次事件的演进链条值得复盘:
text
第一幕: CVE-2026-29146
- 问题: EncryptInterceptor 存在敏感数据加密缺失(部分消息未按预期加密)
- 修复: 随 11.0.20 / 10.1.53 / 9.0.116 发布
第二幕: CVE-2026-34486(本次)
- 问题: 官方确认上述修复不完整------仍存在未被覆盖的发送路径
- 后果: 部分会话复制消息依旧明文上线,绕过加密保护
- 修复: 随 11.0.21 / 10.1.54 / 9.0.117 发布
原理级提示:拦截器栈要保证"所有出站消息必经加密",需要覆盖每一条消息发送路径(常规增量、心跳/成员管理之外的边界路径、异常回退路径等)。修复不完整的典型形态,就是主路径堵上了、某条分支路径漏了。具体路径细节以官方公告与补丁为准,本文给出的是原理级示意,便于理解为什么"升级"是唯一根本解。
2.4 加密路径与绕过路径对比
#mermaid-svg-EhaPc5bDMuuSIdvJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EhaPc5bDMuuSIdvJ .error-icon{fill:#552222;}#mermaid-svg-EhaPc5bDMuuSIdvJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EhaPc5bDMuuSIdvJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .marker.cross{stroke:#333333;}#mermaid-svg-EhaPc5bDMuuSIdvJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EhaPc5bDMuuSIdvJ p{margin:0;}#mermaid-svg-EhaPc5bDMuuSIdvJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster-label text{fill:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster-label span{color:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster-label span p{background-color:transparent;}#mermaid-svg-EhaPc5bDMuuSIdvJ .label text,#mermaid-svg-EhaPc5bDMuuSIdvJ span{fill:#333;color:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .node rect,#mermaid-svg-EhaPc5bDMuuSIdvJ .node circle,#mermaid-svg-EhaPc5bDMuuSIdvJ .node ellipse,#mermaid-svg-EhaPc5bDMuuSIdvJ .node polygon,#mermaid-svg-EhaPc5bDMuuSIdvJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .rough-node .label text,#mermaid-svg-EhaPc5bDMuuSIdvJ .node .label text,#mermaid-svg-EhaPc5bDMuuSIdvJ .image-shape .label,#mermaid-svg-EhaPc5bDMuuSIdvJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-EhaPc5bDMuuSIdvJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .rough-node .label,#mermaid-svg-EhaPc5bDMuuSIdvJ .node .label,#mermaid-svg-EhaPc5bDMuuSIdvJ .image-shape .label,#mermaid-svg-EhaPc5bDMuuSIdvJ .icon-shape .label{text-align:center;}#mermaid-svg-EhaPc5bDMuuSIdvJ .node.clickable{cursor:pointer;}#mermaid-svg-EhaPc5bDMuuSIdvJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .arrowheadPath{fill:#333333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EhaPc5bDMuuSIdvJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EhaPc5bDMuuSIdvJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EhaPc5bDMuuSIdvJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster text{fill:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ .cluster span{color:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EhaPc5bDMuuSIdvJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EhaPc5bDMuuSIdvJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-EhaPc5bDMuuSIdvJ .icon-shape,#mermaid-svg-EhaPc5bDMuuSIdvJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EhaPc5bDMuuSIdvJ .icon-shape p,#mermaid-svg-EhaPc5bDMuuSIdvJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EhaPc5bDMuuSIdvJ .icon-shape .label rect,#mermaid-svg-EhaPc5bDMuuSIdvJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EhaPc5bDMuuSIdvJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EhaPc5bDMuuSIdvJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EhaPc5bDMuuSIdvJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 主路径: 经过 EncryptInterceptor
未覆盖路径: 绕过加密
节点A DeltaManager
会话增量消息
Tribes Channel 出站队列
消息发送路径
AES 对称加密
密文上线
节点B 解密并应用增量
✅ 链路窃听不可还原
明文消息直接上线 ❌
链路中间人/镜像抓包
还原序列化 Session
登录态/PII/SessionID
❌ 会话劫持与数据泄露
免责声明:出于合规考虑,本文不提供任何利用代码,流量验证示例仅用于自有环境的加密有效性巡检。
三、漏洞检测排查
3.1 第一步:确认 Tomcat 版本
bash
# 方式1: 官方 version 脚本(读的是 jar 包版本,注意与运行进程区分,见 4.5 踩坑1)
$CATALINA_HOME/bin/version.sh
# 输出关键行:
# Server version: Apache Tomcat/10.1.53
# 方式2: 直接读 catalina.jar 清单
unzip -p $CATALINA_HOME/lib/catalina.jar META-INF/MANIFEST.MF \
| grep -E "Implementation-Version"
# 方式3: 运行中进程的真实版本(JMX / Manager 页面)
# 通过 jmx_exporter 的 jvm_info 或 catalina 指标确认运行时版本
curl -s http://localhost:9090/metrics | grep -E 'catalina_(start|version)'
Spring Boot 内嵌 Tomcat 排查(最容易遗漏的角落):
bash
# fat jar 里翻内嵌 Tomcat 版本
unzip -l app.jar | grep tomcat-embed-core
# BOOT-INF/lib/tomcat-embed-core-10.1.53.jar ← 版本号直接可见
# 或在 CI 里固化依赖树检查
mvn dependency:tree -Dincludes=org.apache.tomcat.embed:tomcat-embed-core
内嵌 Tomcat 通常不会启用集群会话复制(Spring Boot 应用一般用外置 Session 存储),受本洞影响概率低,但版本基线治理不能漏掉它。
3.2 第二步:确认集群与 EncryptInterceptor 配置
bash
# 确认是否启用集群会话复制
grep -n "SimpleTcpCluster\|DeltaManager\|BackupManager" $CATALINA_HOME/conf/server.xml
# 确认 EncryptInterceptor 是否启用
grep -n "EncryptInterceptor" $CATALINA_HOME/conf/server.xml
# 确认 CATALINA_BASE 与实际加载的 server.xml(多实例部署常见坑)
ps -ef | grep catalina | grep -o "catalina.base=[^ ]*"
grep -n "EncryptInterceptor" $CATALINA_BASE/conf/server.xml
3.3 第三步:流量级验证(自有环境)
bash
# 在复制端口抓包抽查(默认 Receiver 端口 4000,多实例可能不同,以 server.xml 为准)
# 目的: 确认链路上是否存在明文会话内容
tcpdump -i eth0 -w /tmp/replication_check.pcap port 4000 -c 100000 &
# 触发一些会话变更(登录、操作、登出)后停止抓包
strings /tmp/replication_check.pcap | grep -a "JSESSIONID" | head -5
strings /tmp/replication_check.pcap | grep -a "org.apache.catalina.session" | head -5
# 判读:
# - 全部为密文块、无可读 session 字段 → 主路径加密生效
# - 出现可读序列化字段 → 存在明文路径,按受影响处置
注意 :抓包验证务必区分"业务口"与"复制口"。HTTP 响应头里的
Set-Cookie: JSESSIONID本来就是应用层协议内容(HTTPS 环境下也不会出现在网线明文里),抓到它不代表会话复制泄露,别把自己吓到(见 4.5 踩坑5)。
3.4 受影响判定清单
text
□ 部署是否以集群模式运行(SimpleTcpCluster)? 否 → 基本不受影响,纳入版本基线管理
□ 是否启用 EncryptInterceptor? 否 → 不受本洞影响,但检查链路是否本来就明文
□ 版本是否为 11.0.20 / 10.1.53 / 9.0.116? 是 → 按第四节处置
□ 复制流量是否穿越不可信网段(跨机房/云对等/共享专线)? 是 → 优先级上调
□ Session 中是否存放敏感字段(PII/Token/权限)? 是 → 优先级上调,并考虑数据瘦身
四、修复方案
4.1 方案选型矩阵
| 方案 | 适用场景 | 生效速度 | 根治性 | 风险 |
|---|---|---|---|---|
| A: 滚动升级至修复版本 | 所有受影响集群 | 中(需窗口) | ✅ 根本解 | 滚动期间会话切换波动 |
| B: 网络层隔离 | 无法立即升级的过渡 | 快 | ⚠ 缓解 | 隔离不彻底则形同虚设 |
| C: 外置会话存储(Redis 等) | 长期架构演进 | 慢 | ✅ 釜底抽薪 | 架构改造量 |
推荐组合 :B 先行止血(30 分钟内可完成)→ A 跟进根治(72 小时内)→ C 作为中长期目标纳入架构规划。特别提醒:不要用"直接删除 EncryptInterceptor"作为缓解------那会把"部分路径明文"升级为"全量明文",除非你已先完成 B 的网络隔离。
4.2 方案 A:6 节点集群滚动升级脚本
bash
#!/bin/bash
# rolling_upgrade_tomcat.sh - Tomcat 集群滚动升级(CVE-2026-34486)
# 用法: ./rolling_upgrade_tomcat.sh <new_version> <dist_path>
# 约定: 节点列表按"非交易高峰顺序"排列;LB 侧已支持节点摘除
set -euo pipefail
NEW_VERSION="${1:?用法: $0 <new_version> <dist_path>}"
DIST_PATH="${2:?用法: $0 <new_version> <dist_path>}"
NODES=("tomcat-node1" "tomcat-node2" "tomcat-node3" \
"tomcat-node4" "tomcat-node5" "tomcat-node6")
CATALINA_BASE_DIR="/opt/tomcat"
BACKUP_DIR="/opt/backup/tomcat-$(date +%Y%m%d%H%M)"
for NODE in "${NODES[@]}"; do
echo "=== [$(date +%T)] 开始处理节点: $NODE ==="
# 1. LB 摘除该节点(此处以健康检查接口降级为例,实际可对接 LB API)
ssh "$NODE" "touch $CATALINA_BASE_DIR/webapps/ROOT/META-INF/MAINTENANCE"
echo "节点已摘除,等待存量会话 Drain(120s)..."
sleep 120
# 2. 备份配置与 lib(conf/webapps/lib 三样保命)
ssh "$NODE" "mkdir -p $BACKUP_DIR && \
cp -a $CATALINA_BASE_DIR/conf $BACKUP_DIR/ && \
cp -a $CATALINA_BASE_DIR/lib $BACKUP_DIR/"
# 3. 停服、替换、清理旧二进制
ssh "$NODE" "$CATALINA_BASE_DIR/bin/shutdown.sh; sleep 15"
ssh "$NODE" "pgrep -f 'catalina.startup.Bootstrap' && \
echo '进程未退出,强制处理' && pkill -f 'catalina.startup.Bootstrap'; sleep 5"
scp "$DIST_PATH/apache-tomcat-${NEW_VERSION}.tar.gz" "$NODE:/tmp/"
ssh "$NODE" "cd $CATALINA_BASE_DIR && \
tar xzf /tmp/apache-tomcat-${NEW_VERSION}.tar.gz --strip-components=1 && \
rm -rf $CATALINA_BASE_DIR/webapps/{docs,examples,manager,host-manager}"
# 4. 恢复原 conf 与自定义 lib(防止新包覆盖配置)
ssh "$NODE" "cp -a $BACKUP_DIR/conf/* $CATALINA_BASE_DIR/conf/"
# 5. 启动与双重复核:jar 版本 + 进程启动时间
ssh "$NODE" "$CATALINA_BASE_DIR/bin/startup.sh"
sleep 30
ssh "$NODE" "bash $CATALINA_BASE_DIR/bin/version.sh | grep 'Server version'"
ssh "$NODE" "ps -eo pid,lstart,cmd | grep 'catalina.startup.Bootstrap' | grep -v grep"
# 6. 健康检查 + 集群成员确认后才放行下一个节点
ssh "$NODE" "curl -fsS http://localhost:8080/actuator/health" \
|| { echo "健康检查失败,中止滚动并告警"; exit 1; }
ssh "$NODE" "rm -f $CATALINA_BASE_DIR/webapps/ROOT/META-INF/MAINTENANCE"
echo "=== $NODE 完成,集群观察 90s ==="
sleep 90
done
echo "全部节点升级完成,执行最终抓包复核(见 3.3)"
升级顺序要点 :逐台执行、每台放行前确认健康检查通过且集群成员重新收敛(看各节点日志中
Member added恢复完整成员列表)。滚动全程业务不中断,但节点数减少期间冗余度下降,避免与变更窗口叠加。
4.3 方案 B:网络层隔离临时缓解
bash
# 目标: 会话复制流量只允许在集群节点间互通,杜绝穿越不可信网段
# 以复制端口 4000 为例(多实例请按实际 Receiver 端口调整)
# 1. 各节点间白名单(示例在 node1 上,其余节点同理)
for PEER in 10.20.0.11 10.20.0.12 10.20.0.13 10.20.0.14 10.20.0.15; do
iptables -A INPUT -p tcp --dport 4000 -s "$PEER" -j ACCEPT
done
iptables -A INPUT -p tcp --dport 4000 -j DROP
# 组播成员管理口(默认 228.0.0.4:45564)按需同样收敛
# 2. 跨机房专线场景: 严禁复制流量上公网/WAN
# 若拓扑无法避免,用隧道封装(以 WireGuard 为例)
# wg-quick up wg-replication && 调整 Receiver 的 address 绑定到 wg 网卡
# 3. 完成后立即抓包复核(3.3 步骤),确认不再出现明文会话内容
4.4 方案 C:外置会话存储演进
从根上消除"节点间序列化 Session 裸奔"的架构手段,是将会话外置到集中式存储(如 Redis),让 Tomcat 回归无状态计算节点:
- Spring Session(Redis)+
SessionRepositoryFilter,业务代码零侵入 - 复制链路消失,本洞及同类"集群流量加密"问题从此不再适用
- 代价:引入 Redis 高可用治理、序列化兼容性管理、网络延迟评估
这是架构级改造,建议列入下季度技术债清单,而不是本轮应急动作。
4.5 真实踩坑案例
踩坑 1:jar 版本是新的,进程还是旧的------"假升级"
升级脚本执行完,version.sh 显示 10.1.54,抓包复核却发现明文依旧。排查发现:version.sh 每次都新起 JVM 读 jar,显示的是磁盘版本;而常驻进程未重启,内存里还是旧类。教训:验证必须用 ps -o lstart 看进程启动时间 + JMX 运行时版本双确认,脚本里那两行复核不是摆设。
踩坑 2:滚动过快,新旧节点混跑,用户批量掉线
第一轮演练时为赶进度,两台节点间隔只留了 20 秒。混跑期间 DeltaManager 反序列化增量失败,部分用户 Session 丢失被踢下线,客服工单短暂上升。教训:节点间必须等集群成员收敛(各节点日志 Member added 完整)再放行下一台,最终版本 90 秒观察期不可压缩。
踩坑 3:缓解时直接删除 EncryptInterceptor,没先做网络隔离
某兄弟团队为"尽快消除绕过风险"直接摘掉了 EncryptInterceptor,结果抓包复核发现全量复制流量明文------比"部分路径明文"更糟,且恰好那段时间复制流量要走共享专线。教训:缓解的顺序铁律是"先隔离、后降级",任何加密能力的移除都必须以网络面收敛为前提。
踩坑 4:内嵌 Tomcat 漏排
自查时 6 台独立 Tomcat 都确认了版本,但例行 SBOM 扫描在某个 Spring Boot 网关服务的 fat jar 里翻出了 tomcat-embed-core-10.1.53。所幸该服务未启用集群会话复制,不受本洞影响,但暴露了版本基线只盯"独立 Tomcat"的盲区。教训:把 tomcat-embed-core 纳入 SBOM 与统一版本基线,独立部署与内嵌依赖一并盘点。
踩坑 5:抓包脚本误报"会话泄露实锤"
巡检脚本第一版把业务口 8080 上抓到的 Set-Cookie: JSESSIONID 当作明文 Session 泄露,全员凌晨拉群。实际上那是 HTTP 应用层响应头(本就走业务协议),跟复制链路加密无关。教训:检测脚本要锁定复制端口(如 4000),并对匹配规则做"序列化特征 + 复制端口"双条件判定,避免误报透支信任。
五、监控告警建设
5.1 Prometheus 告警规则
yaml
# tomcat-cluster-security-rules.yaml
groups:
- name: tomcat-cluster-security
rules:
# 1. 集群节点版本不一致(滚动升级卡壳/漏升级的第一信号)
- alert: TomcatClusterVersionMismatch
expr: count(count by (version) (tomcat_build_info{job="tomcat"})) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "Tomcat 集群出现多版本共存"
description: "节点版本: {{ $labels.version }},滚动升级未收敛或存在漏升级节点"
# 2. 活跃会话突降(复制失败/反序列化异常/节点掉线的综合信号)
- alert: TomcatActiveSessionSuddenDrop
expr: |
(catalina_manager_active_sessions - catalina_manager_active_sessions offset 5m)
/ catalina_manager_active_sessions offset 5m < -0.30
for: 2m
labels:
severity: warning
annotations:
summary: "节点 5 分钟内活跃会话下降超 30%"
description: "实例 {{ $labels.instance }},可能是集群成员异常或 Session 丢失"
# 3. 集群复制异常计数(jmx_exporter 自定义 rule 聚合 DeltaManager 失败指标)
- alert: TomcatReplicationErrors
expr: increase(tomcat_replication_error_total[10m]) > 0
for: 0m
labels:
severity: warning
annotations:
summary: "会话复制出现失败计数增长"
description: "实例 {{ $labels.instance }},检查集群成员状态与网络连通性"
# 4. 复制链路明文巡检结果(textfile_collector / Pushgateway 上报,1=发现明文)
- alert: TomcatReplicationPlaintextDetected
expr: tomcat_replication_plaintext_check_result == 1
for: 0m
labels:
severity: critical
annotations:
summary: "复制链路明文巡检命中"
description: "抓包复核发现明文会话内容,立即核查版本与 EncryptInterceptor 状态"
5.2 例行抓包巡检
把 3.3 的抓包验证固化为月度巡检任务,结果经 Pushgateway 上报,触发上面的第 4 条规则:
bash
# /opt/scripts/replication_plaintext_check.sh(节选)
# 每月 1 日凌晨执行;在复制口抓 10 万包做明文特征检测
PCAP=$(mktemp /tmp/rep_XXXX.pcap)
timeout 300 tcpdump -i eth0 -w "$PCAP" port 4000 -c 100000 2>/dev/null
if strings "$PCAP" | grep -aq "org.apache.catalina.session"; then
echo "tomcat_replication_plaintext_check_result 1" | curl --data-binary @- \
http://pushgateway:9091/metrics/job/replication_plaintext_check
else
echo "tomcat_replication_plaintext_check_result 0" | curl --data-binary @- \
http://pushgateway:9091/metrics/job/replication_plaintext_check
fi
rm -f "$PCAP"
六、成本核算与 ROI
以本案例 6 节点电商集群、按运维工程师时薪 ¥210 计算:
| 支出项 | 工作量 | 成本 |
|---|---|---|
| 情报确认与影响面盘点(含内嵌 Tomcat 排查) | 1.5h × 2人 | ¥630 |
| 网络隔离临时缓解 + 抓包复核 | 1h × 2人 | ¥420 |
| 滚动升级与逐台验证 | 3h × 2人 | ¥1,260 |
| 监控规则 + 月度巡检脚本固化 | 2h × 1人 | ¥420 |
| 应急总投入(含沟通协调取整) | --- | ≈ ¥2,800 |
| 年度持续成本(巡检 6h + 规则维护 4h) | --- | ≈ ¥2,100 |
对比一次"会话批量泄露"事故的典型代价:
| 事故代价项 | 估算 |
|---|---|
| 账号盗用取证与溯源 | ¥150,000 |
| 用户安抚、密码重置与客服成本 | ¥100,000 |
| 合规整改与通报流程 | ¥80,000 |
| 商誉与转化率损失 | ¥150,000 |
| 单次事故总代价 | ¥480,000 |
按年度发生概率 20% 折算期望损失约 ¥96,000;年度总投入约 ¥4,900,年度净收益约 ¥91,000、ROI 约 18.6 倍。
坦率地说,这是一个"影响面窄"的漏洞,ROI 低于 RCE 类漏洞(如 051 篇的 20 倍)。但两点决定了它不值得拖延:一是命中配置的用户正处于"以为加密了"的最脆弱状态;二是修复成本极低(半天窗口 + 一次滚动),拖延收益为零。
七、总结与最佳实践
7.1 处置检查清单
text
□ 确认 Tomcat 版本(独立部署 + 内嵌 tomcat-embed-core 双线盘点)
□ 确认是否启用集群会话复制与 EncryptInterceptor
□ 受影响版本(11.0.20/10.1.53/9.0.116)→ 升级 11.0.21/10.1.54/9.0.117
□ 无法立即升级 → 先网络隔离复制端口,抓包复核无明文
□ 滚动升级逐台验证:进程启动时间 + 运行时版本 + 集群成员收敛
□ Session 敏感字段瘦身(PII/Token 尽量外置或加密后存放)
□ 固化月度复制链路明文巡检 + Prometheus 版本一致性告警
□ 中长期评估外置会话存储(Spring Session + Redis)根治方案
7.2 陷阱速查表
| 陷阱 | 后果 | 正确姿势 |
|---|---|---|
| 删除 EncryptInterceptor 当缓解 | 部分明文 → 全量明文 | 先网络隔离,再谈加密降级 |
| 只看 version.sh 结果 | "假升级",进程仍跑旧类 | 进程启动时间 + 运行时版本双确认 |
| 滚动间隔过短 | 反序列化失败,用户掉线 | 等集群成员收敛再放行下一台 |
| 只排查独立 Tomcat | 内嵌 tomcat-embed-core 漏网 | SBOM 纳管,双线盘点 |
| 抓业务口判泄露 | 误报透支巡检可信度 | 锁定复制端口 + 序列化特征双条件 |
7.3 "补丁绕过"类漏洞的方法论
- 修复过 ≠ 安全过:对"修复不完整"型漏洞,上一轮的修复台账反而是本轮最大的认知盲区。CVE 公告出来第一件事是对照版本号,而不是翻自己的工单。
- 评分分歧看配置:9.8 与 7.5 之间隔着一个"你是否命中漏洞配置"。按自身架构定风险,按风险定排期。
- 加密有效性要验证:任何"我们配了加密"的假设,都值得一次抓包复核。加密能力是配置出来的,不是承诺出来的。
附录:速查命令
bash
# 版本确认
$CATALINA_HOME/bin/version.sh | grep "Server version"
unzip -p $CATALINA_HOME/lib/catalina.jar META-INF/MANIFEST.MF | grep Implementation-Version
unzip -l app.jar | grep tomcat-embed-core # 内嵌 Tomcat
# 配置确认
grep -n "SimpleTcpCluster\|EncryptInterceptor\|DeltaManager" $CATALINA_BASE/conf/server.xml
ps -ef | grep -o "catalina.base=[^ ]*" # 多实例确认加载路径
# 流量验证
tcpdump -i eth0 -w /tmp/check.pcap port 4000 -c 100000
strings /tmp/check.pcap | grep -a "org.apache.catalina.session"
# 进程真实性复核
ps -eo pid,lstart,cmd | grep 'catalina.startup.Bootstrap' | grep -v grep
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| 11.0.x | 11.0.20 | 11.0.21 |
| 10.1.x | 10.1.53 | 10.1.54 |
| 9.0.x | 9.0.116 | 9.0.117 |
参考资料
- Apache Tomcat Security Pages(各分支官方安全公告): https://tomcat.apache.org/security.html
- NVD - CVE-2026-34486: https://nvd.nist.gov/vuln/detail/CVE-2026-34486
- NVD - CVE-2026-29146(前一环): https://nvd.nist.gov/vuln/detail/CVE-2026-29146
- Tomcat Cluster HowTo(集群与会话复制官方文档): https://tomcat.apache.org/tomcat-10.1-doc/cluster-howto.html
真实性声明
本文案例取材于作者所在团队真实的集群安全巡检与升级实践,环境信息、主机名、密钥与 IP 均已脱敏或替换;漏洞版本信息、修复版本、评分分歧与配置原理均以 Apache Tomcat 官方安全公告及 NVD 公开信息为准。文中截图位置以图片占位符标注,发布时按平台规范补充实拍图。抓包验证示例仅限自有环境合规巡检使用。