Web Challenge Methodology
This is the order I work through any web CTF challenge. It is intentionally boring — most challenges are solved by people who are systematic, not people who are clever.
Phase 1 — Recon (5 min, max)
- Visit the URL. Note the title, status code, server header.
- View source on every page reachable from the index without auth.
robots.txt,sitemap.xml,/.git/,/admin,/api,/debug.- Open the Network tab — what XHRs /
fetches fire on page load? - If source is provided: open it before clicking anything.
Phase 2 — Identify the framework
The framework tells you the bug class. Heuristics:
| Sign | Framework | Look for |
|---|---|---|
__cfduid, cf-ray | Cloudflare in front — bypass IP filter via Host header tricks | |
connect.sid cookie | Node + Express | session secret leak, prototype pollution |
csrftoken, sessionid | Django | SECRET_KEY leak → cookie forgery |
XSRF-TOKEN, laravel_session | Laravel | APP_KEY leak → unserialize gadget |
PHPSESSID | PHP | type juggling, file upload, include LFI |
JSESSIONID | Java / Spring | deserialisation, Spring Boot Actuator endpoints |
_session_id (Ruby) | Rails | secret_key_base leak → cookie session deserialisation |
Asp.NET __VIEWSTATE | ASP.NET | viewstate decryption with machine keys |
Phase 3 — Read the source (if provided)
In order of yield:
- Routing file — what endpoints exist, what auth gates apply.
- Auth code — JWT verifier (look for
nonealgo, weak secret), session logic, magic links. - Diff from the framework default — anything custom is suspicious.
- Input parsing —
parseInt,JSON.parse,unserialize,eval,Function(...). - Template engine config — server-side template injection (SSTI) hides here.
- File handling — uploads, downloads, includes.
In source-provided challenges, the bug is always in one of the
source files committed after the framework’s initial commit. git log --oneline and read the diffs.
Phase 4 — Standard probes
Run these against every input on every endpoint:
' " \ ) < > { } [ ]
';alert(1)//
{{7*7}}
<%= 7*7 %>
${7*7}
#{7*7}
<%= self %>
__proto__[admin]=1
admin' OR '1'='1
../../../etc/passwd
file:///etc/passwd
http://169.254.169.254/What the input does with each probe narrows the bug class quickly.
Phase 5 — The cheap classics
If nothing has bitten yet, in this order:
- JWT — algo
none, weak HMAC secret,kidLFI/SQLi - IDOR — change any id to any other id
- Race condition — fire 20 requests in parallel to “buy”, “redeem”, “send”
- Cache poisoning —
X-Forwarded-Host,X-Original-URL - Web cache deception —
/account/me.cssreturning your account data - OAuth misconfig —
redirect_urisubstring vs equality, missing PKCE - CORS misconfig —
Access-Control-Allow-Origin: *with credentials - CRLF —
%0d%0aSet-Cookie: - SSRF —
image_url=http://localhost:8080/ - SSTI —
{{config}},{{''.__class__.__mro__}} - Prototype pollution —
__proto__[isAdmin]=true - Hop-by-hop —
Connection: X-API-Keystrips internal auth
Phase 6 — Reading the docker-compose
For challenges with docker-compose.yml:
- Are there extra services not exposed externally? (Redis, Postgres, internal API.) Those are SSRF targets.
- Is there a
command:override that hints at how the app starts? - Are environment variables defaulting to weak values?
Phase 7 — When stuck
Walk to make a coffee. The bug is almost always something you saw in Phase 3 and discarded as “probably not it.” Re-read Phase 3 with fresh eyes.