The Marathon
Today I sat down and didn't get up until I'd solved 20 PortSwigger labs. Eight vulnerability classes. One session. My chai went cold three times.
This wasn't about speed — it was about building muscle memory. The kind where you see a template expression in an error message and your fingers are already typing the detection polyglot before your brain catches up.
Here's everything I learned, organized by vulnerability class. Payloads first, explanations after — the way I wish every blog post was written.
Server-Side Template Injection (5 labs)
SSTI is the #1 technique of 2025 according to PortSwigger's annual ranking. Reports increased 540% in 2026. If user input touches a template engine, you can often get RCE.
I solved labs across five different engines: Tornado (Python), Freemarker (Java), Handlebars (Node.js), Django (Python), and Freemarker again with a sandbox escape.
The detection approach is always the same — fuzz with math expressions in both double-curly and dollar-brace syntax. Error messages reveal the engine name. Then consult the engine's documentation for code execution primitives.
Key insight: when your input lands inside an existing template expression (code context), you need to break out first by closing the expression, then inject your own block tags. This is analogous to breaking out of HTML attribute context in XSS.
The sandbox escape was the most interesting. Even with Freemarker's sandbox enabled, you can chain Java reflection methods: getClass to getProtectionDomain to getCodeSource to getLocation to toURI to resolve to toURL to openStream to readAllBytes. The output comes back as decimal ASCII bytes that you convert to a string. Sandboxes that allow getClass() on any object are fundamentally broken.
Bug bounty takeaway: Look for user-controllable values that render server-side — display names, email template editors, CMS page builders, notification templates. Test with both curly-brace and dollar-brace syntax.
SQL Injection — XML Encoding WAF Bypass
When the injection point is inside an XML body (like a stock check feature), WAFs that block SQL keywords in plain text can be bypassed by converting each character to XML hex entities. The XML parser decodes the entities before the SQL executes. The WAF never sees the keywords.
I used a UNION SELECT with concatenation to pull usernames and passwords from the users table, all encoded as hex entities. Logged in as administrator on the first try.
Bug bounty takeaway: Any XML-based API — SOAP services, payment processors, stock checkers. If a WAF blocks your payload, cycle through encoding schemes: XML entities, Unicode normalization, double URL encoding, HTML entities.
NoSQL Operator Injection — Full Account Takeover
This was the most complex lab. A MongoDB-backed login endpoint accepted JSON and was vulnerable to operator injection. I used the $ne operator to bypass password validation, then leveraged the $where clause for server-side JavaScript execution.
The full chain: enumerate all field names on the user object using Object.keys() with regex matching (character by character), discover a password reset token field called changePwd, extract the token value the same way, then use it to reset the target's password and log in.
The whole extraction took about 3 minutes of automated requests. In a real engagement, you'd script this — the boolean oracle (Account locked vs Invalid credentials) makes it perfectly reliable.
Bug bounty takeaway: Any MongoDB-backed JSON API. Test $ne, $gt, and $regex operators on every input field. If you get differential responses, you have a boolean oracle for blind extraction.
Path Traversal — Five Bypass Techniques
I solved five path traversal labs, each with a different filter bypass. Together they form a complete bypass checklist:
- Absolute path: Use /etc/passwd directly when traversal sequences are blocked but absolute paths aren't
- Non-recursive strip: Use ....// which becomes ../ after a single strip pass
- Double URL encode: Use %252f which the server decodes twice to get /
- Start-of-path validation: Prepend the expected directory then traverse out — /var/www/images/../../../etc/passwd
- Null byte: Append %00.png to truncate extension validation
Bug bounty takeaway: Image loading endpoints, file download features, PDF generators. Always test all five techniques — different filters fail to different bypasses.
OS Command Injection — Blind Techniques
Two labs covering blind command injection via feedback forms. The email field is the classic injection point.
Time-based detection: inject a ping with count 10 using pipe operators. A 10-second response delay confirms injection.
Output redirection: redirect command output to a writable web directory like /var/www/images/, then read the file through the image loading endpoint. Simple, reliable, and gives you full command output.
Bug bounty takeaway: Any feature that processes user input server-side — feedback forms, contact forms, file processors. The email field is the most common injection point because developers often pass it to sendmail or similar.
XXE, SSRF, Access Control, Business Logic
The remaining five labs were quick wins:
- XInclude attack: When you can't control the full XML document, inject an XInclude directive with parse=text to read files like /etc/passwd through a regular parameter
- SSRF via open redirect: Chain an application's own open redirect with a URL-fetching feature to reach internal services. The stock checker follows the redirect to the internal admin panel
- Multi-step access control: Admin role upgrades had two steps but only step one checked authorization. Sending the confirmation step directly as a low-privilege user worked perfectly
- Referer-based access control: The server only checked if the Referer header contained /admin. Spoofing it with the fetch API's referrer option bypassed the check
- Negative quantity: Adding an expensive item with quantity 1 and a cheap item with quantity -52 reduced the cart total below the store credit balance. Classic integer handling flaw
What I'm Taking Away
Twenty labs sounds like a lot, but the patterns compress beautifully. There are really only a few core principles at work:
- Every filter has a bypass. If one encoding doesn't work, try another. The application and the WAF process input differently — find the gap
- Error messages are your best friend. They reveal template engines, database types, file paths, and framework versions
- Multi-step processes often only check authorization on the first step. Always test each step independently
- The injection point isn't always where you expect it. Email fields, Referer headers, XML attributes, display names — anywhere user input flows into server-side processing
My chai is cold again. But my hands remember these payloads now. That was the point.
— Seetha
No comments yet