📋 What Is It?
Server-Side Request Forgery (SSRF) occurs when an attacker can trick a server into making unintended requests to internal services, cloud metadata endpoints, or external systems. This bypasses firewalls and exposes internal infrastructure.
#10
OWASP Rank
2.72%
Incidence Rate
9.1
Avg CVE Score
⚠️ Common Exploits
- Internal Service Access: Accessing localhost:8080/admin
- Cloud Metadata: Reading AWS credentials at 169.254.169.254
- Port Scanning: Mapping internal network infrastructure
- Firewall Bypass: Accessing services behind firewall
- File Reading: Using file:// protocol to read local files
- DNS Rebinding: Bypassing URL filters via DNS tricks
🔴 Attack Flow
1. App accepts URL parameter for fetching
↓
2. Attacker provides: http://169.254.169.254
↓
3. Server fetches AWS metadata endpoint
↓
4. Returns IAM credentials to attacker
↓
5. BREACH: Full AWS account access!
↓
2. Attacker provides: http://169.254.169.254
↓
3. Server fetches AWS metadata endpoint
↓
4. Returns IAM credentials to attacker
↓
5. BREACH: Full AWS account access!
❌ Vulnerable Code
// Bad: No URL validation - accepts any URL
import requests
@app.route('/fetch', methods=['POST'])
def fetch_url():
url = request.json.get('url')
# Vulnerable! Can access internal services
response = requests.get(url)
return response.text
// Examples of malicious URLs:
# http://localhost:8080/admin
# http://169.254.169.254/latest/meta-data/iam/
# http://192.168.1.1/admin
# file:///etc/passwd
// Bad: No protocol restriction
@app.route('/proxy')
def proxy():
target = request.args.get('url')
# Allows file://, gopher://, dict://, etc.
return urllib.request.urlopen(target).read()
✅ Secure Code
// Good: Whitelist allowed domains
import requests
from urllib.parse import urlparse
ALLOWED_DOMAINS = ['example.com', 'api.example.com']
BLOCKED_IPS = [
'127.0.0.1', 'localhost', # Localhost
'169.254.169.254', # AWS metadata
'10.', '172.16.', '192.168.' # Private ranges
]
@app.route('/fetch', methods=['POST'])
def fetch_url():
url = request.json.get('url')
# Validate URL scheme
if not url.startswith(('http://', 'https://')):
return error("Invalid protocol"), 403
# Parse and validate domain
parsed = urlparse(url)
if not any(domain in parsed.netloc for domain in ALLOWED_DOMAINS):
return error("Domain not whitelisted"), 403
# Block internal IPs
for blocked in BLOCKED_IPS:
if blocked in url:
return error("Access denied"), 403
# Fetch with timeout
try:
response = requests.get(url, timeout=5)
return response.text
except:
return error("Fetch failed"), 500
// Good: Additional DNS validation
import socket
def is_safe_host(hostname):
try:
ip = socket.gethostbyname(hostname)
# Check if IP is private/internal
if ip.startswith(('10.', '172.', '192.168.', '127.')):
return False
return True
except:
return False
✓ Prevention Checklist
- Whitelist allowed domains/URLs only
- Block internal IP ranges (10.x, 192.168.x, 127.x)
- Block cloud metadata IPs (169.254.169.254)
- Allow only http:// and https:// protocols
- Disable redirects or validate redirect targets
- Use network segmentation
- Implement DNS validation
- Disable IMDSv1 on cloud platforms
- Use timeout for external requests
- Monitor outbound connections
🔍 Detection & Tools
Testing Tools:
Burp Suite
SSRFmap
OWASP ZAP
Interactsh
Prevention Libraries:
SafeCurl
ssrf_filter
urllib validators
requests-guard
How to Test:
- Try localhost URLs (127.0.0.1, localhost)
- Test cloud metadata (169.254.169.254)
- Attempt internal IP ranges (10.x, 192.168.x)
- Test file:// protocol
🌍 Real-World Breaches
- Capital One (2019): SSRF on AWS EC2 metadata service led to 100M+ records stolen
- Uber (2016): SSRF used to access internal Uber systems
- Shopify (2017): SSRF allowed reading internal files and metadata
- HackerOne (2018): SSRF on internal services led to data exposure
📌 Quick Tips
- DO NOT accept arbitrary URLs
- DO NOT allow file:// protocol
- DO whitelist allowed domains
- DO block internal IP ranges
- DO disable IMDSv1 on AWS
📜 Compliance
Related Standards:
- CWE CWE-918
- NIST 800-53 SC-7
- OWASP ASVS V5.2.6
- PCI-DSS Requirement 6.5.1