Free-Flagging – GPNCTF 2025 Writeup

Free Flagging
A small PHP challenge with a sneaky bug that can be defeated with just one line of magic. In this blog post, I’m sharing how I solved the Free-Flagging CTF challenge from GPNCTF 2025.
Challenge Summary
This was a web challenge from GPNCTF 2025 called Free-Flagging
.
We were given:
Spawn challenge instance
ncat –ssl free-flagging.gpn23.ctf.kitctf.de 443
As you can see it provides url that led to a PHP web application:-https://stormwood-of-unstoppable-commerce.gpn23.ctf.kitctf.de
When I visited the URL, if I didn’t send a POST request, the server would just show me its source code. It’s like opening the program and seeing exactly how it works.
The Source Code
1 | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { |
What Is This Code Doing?
When you visit the page normally (GET request): The server shows the source code of the file, like if you were reading the code behind a webpage.
When you send a POST request with a guess: The server compares your guess with the flag using md5() (a type of hash function).
- If your guess matches the flag’s hash, it gives you the flag.
- If your guess doesn’t match, it shows you the hash of the flag and your guess.
Seems secure, right but not really guys…
The Bug – PHP Type Juggling
PHP has a weird behavior when comparing values using == (double equals). It doesn’t always check if things are exactly the same. If two values look like numbers, PHP might convert them into numbers before comparing them. This is called type juggling.
Why is this dangerous?
PHP doesn’t always compare things the way we expect. It might treat strings that look like numbers (even if they’re not) as actual numbers.
Here’s the scary part:
1 | "0e1234" == "0e5678" |
Both 0e1234 and 0e5678 are actually numbers in scientific notation:
- 0e1234 means 0 * 10^1234
- 0e5678 means 0 * 10^5678
Both are treated as 0. So PHP thinks they’re the same even though they’re different!
Magic Hash Example
Now let’s talk about magic hashes. MD5 is a type of hash function that turns a string into a fixed-size value. In this challenge, we are comparing two MD5 hashes.
Consider this string QNKCDZO. When we run it through the MD5 hash function, it gives us:
1 | md5("QNKCDZO") = 0e830400451993494058024219903391 |
Notice how it starts with 0e followed by numbers? That’s a magic hash. It looks like scientific notation (i.e., a number), which is dangerous in PHP because of the way PHP handles type comparison.
If the real flag’s hash looks like this:
1 | md5($flag) = 0e12345678901234567890 |
Then PHP compares these two like this:
1 | md5($flag) == md5("QNKCDZO") → TRUE |
PHP thinks both hashes are the same because they both start with 0e. This means the hashes are not the same, but PHP wrongly says they are equal!
The Exploit (One-Line Win)
I sent the magic hash string (QNKCDZO) to the server using a simple POST request.
1 | curl -X POST https://stormwood-of-unstoppable-commerce.gpn23.ctf.kitctf.de -d 'QNKCDZO' |
The server saw QNKCDZO, hashed it, and then compared the hashes. Because of the magic hash bug, PHP thought my guess was the correct flag and it worked!
Flag: GPNCTF{just_php_d01ng_php_th1ng5_abM2zz}
- Title: Free-Flagging – GPNCTF 2025 Writeup
- Author: Aawart K C
- Created at : 2025-06-22 10:00:00
- Updated at : 2025-07-21 20:39:41
- Link: https://blog.aawart.com.np/GPNCTF-Free-Flagging/
- License: This work is licensed under CC BY-NC-SA 4.0.