Back to Cheat Sheets

🌐 Server-Side Request Forgery (SSRF)

OWASP API Security Top 10 - API07

HIGH RISK

📋 What Is It?

Server-Side Request Forgery (SSRF) occurs when an API fetches a remote resource without validating the user-supplied URL. Attackers can trick the server into making requests to internal services, cloud metadata endpoints, or arbitrary external URLs, bypassing firewalls and accessing sensitive data.

API07 OWASP Rank
Internal Network Access
Cloud Metadata Exposure

⚠️ Common Exploits

  • Cloud Metadata Access: Fetch AWS/Azure/GCP credentials from metadata endpoints
  • Internal Network Scanning: Probe internal IPs and ports
  • Local File Access: Read files using file:// protocol
  • Internal API Access: Call internal services not exposed externally
  • Firewall Bypass: Access services protected by firewall
  • Port Scanning: Enumerate open ports on internal systems

🔴 Attack Flow

1. Attacker finds URL parameter in API

2. Sends internal URL: http://169.254.169.254/

3. Server fetches AWS metadata endpoint

4. Returns IAM credentials in response

5. BREACH: AWS account compromised!

❌ Vulnerable Code

// Bad: No URL validation! @app.route('/api/fetch', methods=['POST']) def fetch_url(): url = request.json.get('url') # VULNERABLE: Fetches any URL! response = requests.get(url) return response.text // Attack examples: POST /api/fetch { "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" } ← AWS credentials! { "url": "http://localhost:8080/admin" } ← Internal admin panel! { "url": "file:///etc/passwd" } ← Local file access! // Bad: Webhook without validation @app.route('/api/webhook', methods=['POST']) def register_webhook(): webhook_url = request.json.get('url') # Stores and calls user-provided URL save_webhook(current_user.id, webhook_url) return {'status': 'registered'}

✅ Secure Code

// Good: Strict URL validation import ipaddress from urllib.parse import urlparse ALLOWED_DOMAINS = ['example.com', 'api.partner.com'] BLOCKED_IPS = [ ipaddress.ip_network('127.0.0.0/8'), # localhost ipaddress.ip_network('169.254.0.0/16'), # link-local (AWS metadata) ipaddress.ip_network('10.0.0.0/8'), # private ipaddress.ip_network('172.16.0.0/12'), # private ipaddress.ip_network('192.168.0.0/16'), # private ] def is_safe_url(url): parsed = urlparse(url) # Only allow HTTP/HTTPS if parsed.scheme not in ['http', 'https']: return False # Whitelist allowed domains if parsed.hostname not in ALLOWED_DOMAINS: return False # Resolve hostname and check IP try: ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname)) for blocked_network in BLOCKED_IPS: if ip in blocked_network: return False except: return False return True @app.route('/api/fetch', methods=['POST']) def fetch_url(): url = request.json.get('url') # Validate URL before fetching if not is_safe_url(url): return {'error': 'Invalid URL'}, 400 try: response = requests.get( url, timeout=5, allow_redirects=False # Prevent redirect attacks ) return response.text except requests.RequestException as e: return {'error': 'Failed to fetch'}, 500 // Good: Use predefined resources instead of URLs @app.route('/api/avatar', methods=['POST']) def set_avatar(): # Instead of accepting URL, use resource ID avatar_id = request.json.get('avatar_id') # Fetch from predefined list avatar_url = PREDEFINED_AVATARS.get(avatar_id) if not avatar_url: return {'error': 'Invalid avatar'}, 400 current_user.avatar = avatar_url return {'status': 'updated'}

✓ Prevention Checklist

  • Whitelist allowed domains/protocols
  • Block private IP ranges (RFC1918, link-local)
  • Disable unused URL schemes (file://, gopher://, etc.)
  • Validate and sanitize all user-supplied URLs
  • Disable HTTP redirects or validate redirect targets
  • Use DNS resolution validation
  • Implement network segmentation
  • Block cloud metadata IPs (169.254.169.254)
  • Use resource identifiers instead of URLs when possible
  • Set request timeouts
  • Log all outbound requests
  • Apply principle of least privilege for network access

🔍 Detection & Tools

Testing Tools:

Burp Suite OWASP ZAP SSRFmap Gopherus curl Postman

Prevention Libraries:

SafeCurl SSRF-Filter urllib3 requests

How to Test:

  • Try localhost: http://127.0.0.1, http://localhost
  • Test metadata: http://169.254.169.254/
  • Try file:// protocol for local files
  • Test internal IPs: 10.0.0.1, 192.168.1.1
  • Use URL encoding/obfuscation bypasses
  • Test redirect chains

🌍 Real-World Breaches

  • Capital One (2019): SSRF to AWS metadata led to 100M+ records breach
  • Shopify (2020): SSRF vulnerability allowed internal network access
  • Verizon (2017): SSRF exposed internal systems and credentials
  • Uber (2016): SSRF in internal tools led to data exposure
  • Atlassian (2019): SSRF in Jira allowed AWS metadata access

📌 Quick Tips

  • DO NOT fetch user-supplied URLs without validation
  • DO NOT trust URL schemes beyond HTTP/HTTPS
  • DO NOT allow access to 169.254.169.254
  • DO whitelist allowed domains
  • DO block private IP ranges
  • DO disable redirects or validate targets
  • DO use network segmentation

📜 Compliance

Related Standards:

  • OWASP ASVS V5.2
  • PCI-DSS Requirement 6.5.1
  • NIST 800-53 SC-7
  • ISO 27001 A.13.1.3
  • SOC 2 CC6.6