SSRF Explained: Server-Side Request Forgery Vulnerabilities
Understand Server-Side Request Forgery attacks where attackers manipulate server-side requests to access internal resources, cloud metadata, and bypass firewalls.
When the Server Becomes Your Proxy
A web app lets you fetch a URL to preview a link. You enter http://169.254.169.254/latest/meta-data/ — the cloud metadata endpoint that should only be accessible from within the server. The server fetches it and returns your cloud provider's access keys. This is Server-Side Request Forgery (SSRF): the attacker tricks the server into making requests to internal services, cloud metadata, or systems behind firewalls that should never be exposed.
Prerequisites
Before studying SSRF, you should understand:
How SSRF Works
Applications that fetch remote resources are vulnerable when user input controls the URL. Common functionality includes webhooks, image processing, proxy functionality, API integrations, and URL preview generators.
Basic SSRF Example
import requests
@app.route('/fetch-avatar')
def fetch_avatar():
url = request.args.get('url')
response = requests.get(url)
return response.content
An attacker exploits this to access internal services:
GET /fetch-avatar?url=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: vulnerable-app.com
Types of SSRF
Basic SSRF
The application fetches from user-controlled URLs without restrictions.
Blind SSRF
The attacker cannot see the response but can observe side effects like timing differences or DNS lookups.
Partial SSRF
Some validation exists but is insufficient — attackers use protocol confusion or URL parsing tricks.
SSRF Attack Vectors
Cloud Metadata Endpoints
# AWS metadata
http://169.254.169.254/latest/meta-data/
# GCP metadata
http://metadata.google.internal/computeMetadata/v1/
# Azure metadata
http://169.254.169.254/metadata/instance?api-version=2021-02-01
Internal Service Access
http://localhost:6379/ # Redis
http://localhost:9200/ # Elasticsearch
http://localhost:27017/ # MongoDB
File Protocol Access
file:///etc/passwd
file:///proc/self/environ
file:///app/config/database.yml
SSRF Defenses
Allowlist Approach (Recommended)
ALLOWED_DOMAINS = {'api.trusted-service.com', 'cdn.trusted.com'}
def is_allowed_url(url):
from urllib.parse import urlparse
parsed = urlparse(url)
return parsed.hostname in ALLOWED_DOMAINS
Network-Level Defenses
iptables -A OUTPUT -d 169.254.169.254 -j DROP
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
Disable Unnecessary URL Schemes
ALLOWED_SCHEMES = {'https'}
def safe_fetch(url):
parsed = parse_url(url)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError("Scheme not allowed")
return requests.get(url)
Real-World Examples
Capital One Breach (2019): Paige Thompson exploited an SSRF vulnerability in Capital One's AWS infrastructure to access EC2 metadata, retrieving IAM credentials and accessing over 100 million customer records.
Uber Breach (2016): Attackers found an SSRF vulnerability in Uber's public application, accessed AWS metadata, and stole credentials for S3 buckets containing 57 million user records.
HackerOne SSRF (2017): A researcher found an SSRF vulnerability allowing internal file access, leading to a $10,000 bug bounty payout.
Common Mistakes
Relying on hostname validation alone: DNS rebinding switches from safe to malicious IP after validation.
Insufficient blocklist coverage: Blocking 127.0.0.1 but forgetting IPv6 ::1, or blocking AWS metadata but not GCP or Azure equivalents.
URL parsing inconsistencies: Different libraries parse URLs differently.
Redirect following without re-validation: The application validates the initial URL but follows redirects to internal IPs.
Best Practices
Related Tools
Related Articles
Summary
SSRF allows attackers to turn your server into a proxy to access internal resources. The most effective defense is using an allowlist of trusted domains and validating at both the application and network level. Cloud environments are particularly vulnerable due to accessible metadata endpoints.