Sometimes CTF challenges teach you more about real-world vulnerabilities than any security course. The Equestria series from CyberGame 2025 was exactly that kind of challenge.
Four interconnected web levels. Each one building on the last. Door to the Stable, Shadow Realm, The Dark Ruler, and Final Curse. What started as a simple nginx misconfiguration turned into a masterclass in web application security failures.
The first level gave us an nginx configuration file. Always a good sign when CTF organizers hand you the server config upfront.
nginx
This is a textbook nginx path traversal vulnerability. The
alias directive doesn't chroot the request like you'd expect. It just rewrites the path. Add an off-by-one slash error and you get directory traversal.The trick is in how nginx processes the path. Request
/images../ and nginx sees images.. as a single segment, discards the .. part, then does normal path resolution:Simple curl commands revealed the entire application structure:
bash
Inside the application's
index.js, a hard-coded Basic Auth header sat in plain sight:javascript
Base64 decode that string:
The password was the flag. Classic mistake. Hard-coded credentials in source code that's exposed through a path traversal vulnerability.
Flag 1:
SK-CERT{0ff_by_4_s1ngle_sl4sh_f836a8b1}The second level added a twist. Two authentication layers: Basic Auth (which we already had) and JWT tokens from
/api/login. Only users with both could access /api/notes where the second flag lived.The vulnerability was in the user registration flow. Looking at the source code revealed the issue:
javascript
The database schema showed the default value for
verified was true:sql
But
sendEmailToAdministrator() was just a placeholder:javascript
A one-second window. Register a user (starts as
verified = true), sleep for one second, then flip to verified = false. If you could login during that window, you'd get a valid JWT for a soon-to-be invalid user.The race condition exploit was straightforward:
python
The welcome message contained the flag:
json
Flag 2:
SK-CERT{r4c3_4g41n5t_th3_l1ght_4nd_w1n_w1th_th3_p0w3r_0f_th3_n1ght}Level three required accessing
/api/secret-note, but only privileged users with is_d4rk_pr1nc3ss: true could get there. The JWT verification logic had a critical flaw:javascript
The bug is subtle but devastating. The signature verification uses
parts[parts.length - 2] to calculate the expected signature, but the returned payload comes from parts[1]. This means you can inject additional JWT segments and the signature verification will still pass.A malicious token structure:
The signature gets calculated on
original_payload but the application processes malicious_payload. JWT surgery at its finest.Flag 3:
SK-CERT{1_w1ll_rul3_th3_n1ght_4nd_th3_d4y}The final level was the wildest. SQL injection, but not the kind you'd expect. The vulnerable query builder used JavaScript's
String.prototype.replace() in a dangerous way.The payload:
It exploits JavaScript's replacement pattern tokens. In JavaScript replace operations, `$`` (dollar + backtick) represents the portion of the string before the match. This breaks out of the SQL string context and injects arbitrary SQL.
The tab characters (
\t) aren't filtered and work as token separators in PostgreSQL. The result is a successful UNION injection that dumps all notes from all users.Flag 4:
SK-CERT{j4v4scr1p7_1s_full_of_curs3d_(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(![]+[])[+[]]+(![]+[])[+[]]}This CTF series was brilliant because every vulnerability was realistic. These aren't theoretical bugs. They're the kind of issues that slip into production systems every day.
The nginx alias misconfiguration is a classic. Developers think
alias creates a chroot jail. It doesn't. Combined with autoindex, it becomes a complete source code disclosure vulnerability.Race conditions in authentication flows happen more often than you'd think. Asynchronous operations create timing windows. When those windows affect security-critical state, bad things happen.
JWT implementations are notoriously buggy. Algorithm confusion, signature bypass, payload manipulation. The "algorithm none" attack is well-known, but implementation bugs like the parts array confusion are subtler and more dangerous.
SQL injection through JavaScript string replacement is creative. It shows how language-specific behaviors can create unexpected attack vectors. The interaction between JavaScript's replacement patterns and SQL parsing created a vulnerability that traditional SQL injection filters would miss.
Each level had multiple ways to prevent the attack:
- Use
rootinstead ofaliasin nginx configurations, disable autoindex, or properly validate paths - Make authentication atomic, don't rely on timing-sensitive operations, use well-tested libraries, validate token structure before processing
- Use parameterized queries, validate and sanitize all inputs, use well-tested libraries
The Equestria series proved that security vulnerabilities rarely exist in isolation. The path traversal exposed the hard-coded credentials. The race condition enabled JWT token theft. The JWT bypass allowed privilege escalation. The SQL injection completed the compromise.
Real-world security requires thinking about these attack chains. Fix one vulnerability and attackers find another. Defense in depth means multiple layers of protection, each one designed to catch what the others miss.
That's what made these challenges great. They weren't just isolated bugs to find and exploit. They were a complete attack narrative that showed how small misconfigurations compound into total system compromise.
That's exactly how real security breaches happen.