# 🚩 NI-CTF 2025 - Write-up
| Item | Value |
|---------------------|------------------------------------|
| **CTF** | SunshineCTF 2025-09-27 |
| **Challenge name** | Web Forge |
| **Write-up team** | [CTU FIT] Zuul |
| **Write-up author** | Bc. David Pavluv |
| **Challenge author** | d!!! |
| **Category** | Web |
| **Points** | 363 |
| **Tags** | SSRF, SSTI, RCE, Fuzzing, Payload Evasion |
## Abstract
The challenge combines several vulnerabilities: SSRF, SSTI, and RCE.
> **Tip:** This write-up is a short endorsement of Burp Suite — it made the CTF web challenges a lot easier to solve.
---
## Assignment
<img src="https://hackmd.io/_uploads/SJg9CfKnlg.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
---
## Recon
- `/`
Description: On the main page there's a link to `/fetch` labeled SSRF tool.
Hint 1: Focus on Server-Side Request Forgery (SSRF).
- `/fetch`
<img src="https://hackmd.io/_uploads/H1ixrXt2xe.png" width="300" style="border:1px solid #ddd; border-radius:2px;" />
Hint 2: To access the SSRF tool you must set an access header correctly.
- `/robots.txt`
<img src="https://hackmd.io/_uploads/rkVsZXF3gx.png" width="300" style="border:1px solid #ddd; border-radius:2px;" />
Hint 3: There is an `/admin` endpoint.
Hint 4: An acccess header value that works for `/fetch` is true.
- `/admin`
<img src="https://hackmd.io/_uploads/HJ1RB7Knlx.png" width="300" style="border:1px solid #ddd; border-radius:2px;" />
Hint 5: We don't have access to this resource. Authorization may be implemented via an IP whitelist.
### Preliminary thought process
- **Thought 1:** For access to `/fetch` (SSRF tool) we know the header value but not the header name. There aren't that many common header names used for access, and since fuzzing is allowed in the challenge, brute-forcing header names is a reasonable approach.
- **Thought 2:** If we gain access to the SSRF tool, we can try to fetch `/admin`. In that case the request will originate from the server hosting the SSRF tool — its IP might be on the whitelist.
---
## Step-by-step solution
### Step 1: Bruteforce the access header name
I intercepted the request to ``/fetch`` in Burp Suite and sent it to Intruder. I configured Intruder to enumerate possible header names and used this header wordlist: https://github.com/h0tak88r/Wordlists/blob/master/Headers.txt.
<img src="https://hackmd.io/_uploads/rkgCOBY2gl.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
After running the brute-force attack, we discovered the header that worked: `allow: true`.
<img src="https://hackmd.io/_uploads/SyzMtBFngx.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
Using Burp Suite I replayed that exact request in a browser. The page loaded and presented a field to enter a URL; when I entered http://google.com the SSRF tool returned Google's page content.
<img src="https://hackmd.io/_uploads/S1apsrKnlg.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
### Step 2: Fetch `/admin`
At this point we had access to the SSRF tool, so the next step was to fetch `/admin`. When I requested http://localhost/admin through the SSRF tool I got an error saying the template parameter was missing — but this was still a success, since the request wasn’t blocked by authorization as it had been previously.
<img src="https://hackmd.io/_uploads/HJ6DpSYhlx.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
### Step 3: The `template` parameter
After I supplied the `template` parameter the response changed: it reported that the connection could not be established.
<img src="https://hackmd.io/_uploads/rkwXG8Fnee.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
This suggested the service might be listening on a different port. I tried several common web ports and found that port 8000 worked.
<img src="https://hackmd.io/_uploads/SyIm7Ltnge.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
### Step 4: SSTI in the `template` parameter
The parameter name template was a hint that there might be a Server-Side Template Injection (SSTI). The PortSwigger article on SSTI (https://portswigger.net/web-security/server-side-template-injection) was very useful for identifying template engine.
<img src="https://hackmd.io/_uploads/SJFHvUYhxx.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
In our case, the server appeared to be using **Jinja2** or **Twig**.
### Final step 5: From SSTI to RCE
After narrowing the template engine to Jinja2/Twig, I searched for engine-specific payloads (for example, on https://onsecurity.io/article/server-side-template-injection-with-jinja2). Many payloads returned a **"Nope." response**. I discovered that the protection blocked payloads containing the characters `_` or `.`; a payload that avoided both characters successfully executed. I injected an `ls` command and observed the directory listing — **flag.txt** was present.
<img src="https://hackmd.io/_uploads/HJxMq8Fhxg.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />
Attempting to execute `cat flag.txt` failed because the `.` character was blocked, but using a wildcard `cat flag*` bypassed the filter and returned the flag.
<img src="https://hackmd.io/_uploads/rkF8n8tnge.png" width="600" style="border:1px solid #ddd; border-radius:2px;" />