0%

CITCTF2025-5WebChallengesWriteups

Omar Mohamed
Thanks for sharing!

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

CIT CTF 2025 - 5 Web Challenges Writeups
Hello and welcome back again to another walkthrough! Mushroom is here and today I'll be covering all the web challenges from CIT CTF 2025 which I played with Lil L3ak team. We were able to get the 2nd place with all challenges solved except for one crypto challenge. Also securing +$13k in prizes!💰💎
Leader Board
Prizes
Let's start right away 🚀

Challenges


Breaking Authentication

Breaking Authentication
Visiting the url, I got a login form
Login
First thought SQL injection, and indeed it was. I simply logged in with admin' -- and I was redirected to Admin Panel but the flag wasn't there
Since there’s a SQL injection vulnerability, the flag might be stored in the database. However, we face a challenge: there’s no visible output, only a redirect. This indicates a Blind SQL Injection — either Error-Based or Time-Based, both of which are exploitable. To speed things up, I used SQLMap, an automated tool for SQL injection exploitation
I won't get deep into explaining the tool, you can look it up
I saw a table called secrets, so next let's dump its data:
Flag
CIT{36b0efd6c2ec7132}

Commit and Order Version Control Unit

Chal
As from the name.. this have something to do with git which is often used for version control. Sometimes developers mistakenly leave it out open in the wild and you can access it via /.git/
Accessing it got me Forbidden
Forbidden
But accessing other core file paths like /.git/HEAD got me response
Success
So it seems that the dev prevents access to directories but not the files themselves.
Now whenever you get an exposed git like this, you can dump it with tools like git-dumper. Simply run:
Now inspecting this repo we can see 2 file: admin.php & index.php. index.php got me a password:
pass
Spoiler Alert: this pass 9f3IC3uj9^zZ will be used in another challenge
I couldn't log in using that password in this challenge.
If you're familiar with Git (which you really should be), you'd know about commits — essentially snapshots of your codebase at different points in time. You can navigate to any of these commits to see the code as it was before any updates or deletions. In our case, that's a great thing — it means we can recover deleted content this way!
You can view past commits along with their messages using:
git log
To navigate to a specific commit, run:
and you'll see the files as they were at that point in time.
Extra: to get back to the latest commit just run:
I used another option to speed things up:
This will show differences between your version and the past version, which reveals the past code for you as well!
I eventually got the flag in the 4th commit base64 encoded: Q0lUezVkODFmNzc0M2Y0YmMyYWJ9
flag
CIT{5d81f7743f4bc2ab}

How I Parsed your JSON

chal
Now that's a good one!
We've got this page
main page
To sum things up, it is acting as a databse but gets data from json files! And we control this filename (Here a path traversal comes to mind ✨)
I tried * and got the following:
data
The url looked like: /select?record=*&container=employees
I checked the page source but didn’t find anything useful, neither the path traversal worked, there was some filtering on the backend
One of the things an attacker loves is an Error! Errors can reveal information about the backend, and if debugging is enabled, they might even leak source code.
Add this to your Notes: Try to break things and trigger errors.
After poking around a bit, I removed the second parameter (container) and got my beautiful TypeError!
Error
Going through it, I got out with some juicy code
leaked Code
leaked Code
The code is simple — it takes your container query and passes it to the clean_container_name() function. This function extracts the part before the file extension and removes any ../. The returned value is then passed directly to the os.path.join() function and this path is used to read a file.
For example if you passed ../secrets.txt it will be filtered to secrets and the final path will be containers/secrets
Now here.. did you spot the vulnerability? the code is basically removing ../, but it doesn't do that recursively. What if you put ../ in another ../ to be ....//, so when ../ gets deleted the final result will be ../
Simpler version: Suppose Mush is filtered → I pass MuMushshMush is deleted → result is Mush
That's it! Now pass ....//secrets.txt and ... Invalid Query
Why is that? remeber the os.path.splitext(n)[0], this gets only the first part before the extension and becomes containers/../secretssecrets
You can handle that in two ways:
  • 1 - Use the same technique we just used and pass a ../ in before the extension, so when it is filtered, you end up correctly: ....//secrets.../txt../secrets.txt
  • 2 - The other way is just to url encode the .: ....//secrets%2Etxt
flag
And you get your flag (with the same password we found earlier, you will know its use in the next chal)
CIT{235da65aa6444e27}

Keeping Up with the Credentials

chal
This challenge, along with the next one, had the lowest solve rates (That led me to assume it would involve some tricky or advanced technique and I wasted a long time overthinking it)
The setup was a simple login form, and considering the challenge was titled “Keeping Up with the Credentials”, I tried using the credentials I had found earlier: 9f3IC3uj9^zZ. They worked, and I was logged in… but there was no flag.
While inspecting the login request in Burp Suite, I noticed something odd:
weird login
Instead of using a typical POST request, the form sent a GET request with the credentials in the URL query string. I dismissed it at first, thinking maybe the developer just implemented login that way.
Later, I found an interesting path: /admin.php. When I tried to access it, I was immediately redirected back to the login page, so I assumed access was restricted. I started testing various techniques, including HTTP request smuggling, all based on the assumption that the challenge had to be difficult due to its low solve count.
Turns out… the fix was stupidly simple: I just had to change the login request from GET to POST, and I was redirected to /admin.php, where I found the flag.
Not sure if this challenge was testing secure coding practices or just trolling me a bit 🙂 but either way:
CIT{7bf610e96ade83db}

And now with the last challenge - this one is good

Mr Chatbot

chal
Visiting the url, it asks you for your name:
name
I was redirected to a chatting page:
chat
Not much functionalities, I inspected the page source, it had the follwing script
It's just a simple script that makes the bot give some responses, but the interesting thing… it reflects my name! SSTI? Let's try it out
I passed {{7*7}} and got 7*7 back, so it was filtering. Tried multiple other payloads but all failed.
In your cookies, you’re given a session token, specifically, a Flask session token. It’s similar to a JWT (JSON Web Token) but has a few key differences.
You can easily decode it using Flask-Unsign
You have your name and an admin value set to 0. Mass Assignment?
I sent another request with admin set to 1. This time I got a longer token
I was successfully able to set admin to 1. I tried to access the page again but no differences.
We notice another value added when we did exploit the Mass Assignment: uid, which has what it seems like a base64 encoded value.
After decoding
decoded
It has the name I passed and some bytes: mushroomb'^\xf2\xc3 ... \xd7\x05'. I tried hex decoding that but it didn't give me anything. On the bright side we have a reflection! my name is there
Add this to your Notes: Reflections are good!
Reflection helps identify injection points — where user-controlled data flows — which is critical in exploiting things like SQLi, command injection, or template injection.
To test that I passed {{7*7}} into name and set admin to 1
reflected!
Bingo! It is vulnerable, I got 49. That's our way in!
I used {{''.__class__.__mro__[1].__subclasses__()}} in name and got a huge token
reflected!
decoding it
reflected!
Next I used {{''.__class__.__mro__[1].__subclasses__()[399]('secrets.txt').read()}}
flag
And that's our flag!
CIT{18a7fbedb4f3548f}

Finally this writeup is over, took +4 hours man lol. 😮‍💨
Hopefully that was beneficial to you. Don't forget to check out my X and if you have any questions, feel free to reach out to me on Discord.
See you next time 🚀! Peace.

You might also like