SSRF: The Server Makes the Request
The app fetches a URL the attacker controls. Now the server issues GET requests on behalf of the attacker — to internal IPs, cloud metadata, and services that trust localhost.
The app fetches a URL the attacker controls. Now the server issues GET requests on behalf of the attacker — to internal IPs, cloud metadata, and services that trust localhost.
Server-Side Request Forgery (SSRF) is a class of vulnerability where an attacker tricks the server into making requests to unintended destinations. Instead of attacking the server directly, the attacker uses the server as a proxy to reach internal systems, cloud metadata endpoints, or other services the server can access but the attacker cannot.
SSRF occurs when an application fetches a URL based on user input. Common features that do this include URL preview tools, webhook callbacks, file importers, PDF generators, and proxy endpoints. The server receives a URL from the user, then makes an HTTP request to that URL. If the server does not validate the destination, the attacker can point it at any reachable host — including services bound to localhost, internal RFC 1918 addresses, and cloud metadata endpoints.
The key insight is network topology. The server sits inside a network perimeter with access to resources the attacker cannot reach directly. A database on port 5432, a Redis cache on 6379, a Kubernetes API on 6443 — all are invisible to the public internet but reachable from the server. SSRF turns the server into a bridge between the public internet and the internal network.
SSRF can be blind — the server makes the request but the response is never returned to the attacker — or out-of-band, where the attacker can read the response. Blind SSRF is harder to exploit but still dangerous: even a single DNS lookup to a known host can exfiltrate data through the hostname itself.
The panel below simulates a URL preview feature similar to what messaging apps and social networks use to generate link previews. Enter a URL and the server fetches it. Try http://169.254.169.254/latest/meta-data/ to see how an attacker can reach the cloud metadata service. Toggle safe mode to see how blocking private IP ranges stops the attack.
No requests yet.
In March 2019, a former Amazon employee discovered an SSRF vulnerability in a Capital One Web Application Firewall (WAF) configuration. The WAF sat in front of Capital One's AWS-hosted applications but passed through requests to the AWS metadata endpoint at 169.254.169.254. By sending a crafted request with a modified Host header that pointed to the metadata service, the attacker retrieved the IAM role credentials assigned to the WAF's compute instance.
With those credentials, the attacker listed S3 buckets, downloaded over 100 million credit card applications, and exfiltrated 140 GB of data that included Social Security numbers, bank account numbers, and dates of birth. The breach was detected by a third party who noticed the unusual data transfer. Capital One was fined $80 million by the OCC and $190 million in class-action settlements. The root cause was a single SSRF — no authentication bypass, no SQL injection, just a server that fetched a URL it should not have.
The primary defence is an allowlist of permitted destinations. If the application needs to fetch URLs, restrict the set of allowed hostnames and IP ranges. Block private IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and the cloud metadata IP (169.254.169.254/32) at the network level or in the URL validation logic.
// VULNERABLE - fetches any URL the user provides
const response = await fetch(userProvidedUrl);
return response.text();
// SAFE - validate against an allowlist
const allowedHosts = new Set(['api.example.com', 'cdn.example.com']);
const url = new URL(userProvidedUrl);
if (!allowedHosts.has(url.hostname)) {
throw new Error('destination not allowed');
}
// Also block private IPs after DNS resolution
const ip = await dnsResolve(url.hostname);
if (isPrivateIp(ip)) {
throw new Error('private IP range blocked');
}
const response = await fetch(url);Use a dedicated network policy that denies egress to private ranges. Disable unnecessary URL-fetch features entirely where possible. For cloud environments, enable IMDSv2 which requires a session token and cannot be tricked by a simple Host header manipulation.
1.What is the root cause of an SSRF vulnerability?
2.Why is 169.254.169.254 a common SSRF target?
3.What is the difference between blind and out-of-band SSRF?