These notes are public, opinionated, and evolving — read abdelkader.ma for the long-form posts.
CTF NotesWeb Challenge Methodology

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:

SignFrameworkLook for
__cfduid, cf-rayCloudflare in front — bypass IP filter via Host header tricks
connect.sid cookieNode + Expresssession secret leak, prototype pollution
csrftoken, sessionidDjangoSECRET_KEY leak → cookie forgery
XSRF-TOKEN, laravel_sessionLaravelAPP_KEY leak → unserialize gadget
PHPSESSIDPHPtype juggling, file upload, include LFI
JSESSIONIDJava / Springdeserialisation, Spring Boot Actuator endpoints
_session_id (Ruby)Railssecret_key_base leak → cookie session deserialisation
Asp.NET __VIEWSTATEASP.NETviewstate decryption with machine keys

Phase 3 — Read the source (if provided)

In order of yield:

  1. Routing file — what endpoints exist, what auth gates apply.
  2. Auth code — JWT verifier (look for none algo, weak secret), session logic, magic links.
  3. Diff from the framework default — anything custom is suspicious.
  4. Input parsingparseInt, JSON.parse, unserialize, eval, Function(...).
  5. Template engine config — server-side template injection (SSTI) hides here.
  6. 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, kid LFI/SQLi
  • IDOR — change any id to any other id
  • Race condition — fire 20 requests in parallel to “buy”, “redeem”, “send”
  • Cache poisoningX-Forwarded-Host, X-Original-URL
  • Web cache deception/account/me.css returning your account data
  • OAuth misconfigredirect_uri substring vs equality, missing PKCE
  • CORS misconfigAccess-Control-Allow-Origin: * with credentials
  • CRLF%0d%0aSet-Cookie:
  • SSRFimage_url=http://localhost:8080/
  • SSTI{{config}}, {{''.__class__.__mro__}}
  • Prototype pollution__proto__[isAdmin]=true
  • Hop-by-hopConnection: X-API-Key strips 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.