An SSRF vulnerability lets an attacker trick your server into making network requests on their behalf, and it's the flaw that exposed 106 million records in the Capital One breach. Server-side request forgery happens whenever your application takes a URL or address from user input and fetches it server-side without validating where that request is actually going. Because the request originates from your server, it inherits your server's network position — access to internal services, cloud metadata endpoints, and databases that are firewalled off from the public internet. That trusted position is exactly what makes SSRF so dangerous: the attacker borrows your server's credentials to reach things they never could directly.
This guide explains the SSRF vulnerability class conceptually, walks through the Capital One case as the canonical example, and lays out the defenses that actually hold.
How an SSRF vulnerability arises
The pattern is deceptively ordinary. An application accepts a URL for a legitimate reason — fetching a user's avatar from a link, generating a PDF preview of a webpage, importing data from a supplied endpoint, checking a webhook — and passes it more or less directly into an HTTP client:
# Vulnerable pattern (illustrative)
import requests
def fetch_preview(url):
# url comes straight from the request body
resp = requests.get(url)
return resp.content
If url is fully attacker-controlled and unvalidated, the attacker doesn't have to point it at an external site. They point it inward: at http://localhost:8080/admin, at an internal service on the private network, or — the classic cloud escalation — at the instance metadata service.
The severity ranges from information disclosure (reading internal pages) to full compromise (stealing cloud credentials), which is why OWASP promoted SSRF to its own category, A10, in the OWASP Top 10 2021. It was common and impactful enough to stand on its own.
The cloud metadata escalation
The reason SSRF became a cloud-era headline is the instance metadata service (IMDS). On AWS, EC2 instances expose configuration and — critically — temporary IAM credentials at http://169.254.169.254/, a link-local address reachable only from the instance itself. That "only from the instance itself" is the whole problem: an SSRF vulnerability makes your server issue the request, so from IMDS's perspective, the request is legitimate.
In the 2019 Capital One breach, an attacker exploited an SSRF flaw in a misconfigured web application firewall (ModSecurity) fronting an AWS environment. The SSRF let them coax the WAF into requesting the metadata endpoint's IAM credentials path. IMDS returned temporary credentials for the role attached to the instance, and because that role had broad S3 permissions, those credentials were enough to list and read data from private buckets — over 100 million credit-application records. The breach led to an $80M regulatory penalty and a $190M class-action settlement, and it drove industry-wide adoption of IMDSv2.
Two design flaws compounded: the SSRF itself, and an over-permissioned role. Either one alone would have been far less catastrophic.
Defense 1: validate and constrain the destination
The primary control is to never let user input freely decide the destination of a server-side request. In order of strength:
Allow-list, don't deny-list. If your feature only ever fetches from a known set of domains, enforce exactly that set. Deny-lists fail constantly because attackers have endless bypasses — decimal-encoded IPs (http://2130706433/ for 127.0.0.1), IPv6-mapped addresses, DNS names that resolve to internal IPs, redirect chains, and URL-parser confusion where your validator and your HTTP client disagree about what the host is.
Resolve and check the IP, not just the string. Parse the URL, resolve the hostname to its IP, and reject any address in a private or reserved range (RFC 1918 space, loopback, link-local 169.254.0.0/16, and cloud metadata IPs) before making the request. Do this after DNS resolution, and re-check on redirects, to defeat DNS-rebinding tricks.
import ipaddress, socket
from urllib.parse import urlparse
BLOCKED = [ipaddress.ip_network(n) for n in
("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
"192.168.0.0/16", "169.254.0.0/16", "::1/128")]
def is_safe(url):
host = urlparse(url).hostname
ip = ipaddress.ip_address(socket.gethostbyname(host))
return not any(ip in net for net in BLOCKED)
This snippet is illustrative, not production-ready — a real implementation must handle every resolved address, follow redirects manually with re-validation, and disable automatic redirects in the HTTP client. Frame it as defense in depth, not a silver bullet.
Defense 2: harden the environment so SSRF pays off less
Even a perfect input filter can be bypassed by a bug you didn't anticipate, so reduce what a successful SSRF can reach.
Require IMDSv2. IMDSv2 makes metadata requests session-oriented: a client must first PUT to obtain a token, then include it on the GET. A basic SSRF that can only issue a simple GET can't complete the handshake, which is why enforcing IMDSv2 (setting instance metadata options to http-tokens: required) directly mitigates the Capital One-style attack. Also set the metadata hop limit to 1 so a containerized workload can't reach it.
Least-privilege IAM. If the instance role in the Capital One case had only the S3 access it genuinely needed, the stolen credentials would have been far less useful. Scope roles tightly; assume credentials will leak.
Network egress controls. Put application servers in subnets that can't reach internal admin services or the metadata endpoint unless required, and log outbound requests so anomalous internal fetches are visible.
Detecting SSRF before it ships
SSRF is a data-flow bug — untrusted input reaching a request sink — which makes it findable with static analysis that traces taint from an HTTP parameter to an outbound-request function. SAST rules for semgrep and similar tools flag the pattern of user input reaching requests.get, urllib, fetch, or an HTTP client without an intervening allow-list check. Dynamic testing catches it too: a DAST scan can probe URL-accepting parameters with internal and metadata addresses and watch for tell-tale responses or out-of-band callbacks. Running both static and dynamic checks in CI catches the flaw before it reaches production.
SSRF also hides in dependencies — a vulnerable HTTP-fetching library or a URL-parsing package with a known bypass can introduce it even when your own code is careful. An SCA tool such as Safeguard can flag a transitive component with a known SSRF advisory. For the broader input-validation patterns behind this class of bug, our Academy covers untrusted-input handling in depth.
FAQ
What is an SSRF vulnerability in simple terms?
It's a flaw where an attacker gets your server to make network requests to a destination they control or choose. Because the request comes from your server, it can reach internal services, databases, and cloud metadata endpoints that are blocked from the public internet, turning a URL-fetching feature into a doorway to your internal network.
How did SSRF cause the Capital One breach?
An SSRF flaw in a misconfigured WAF let an attacker make the server request the AWS instance metadata endpoint, which returned temporary IAM credentials. Because the associated role had broad S3 permissions, those credentials were used to read over 100 million records from private buckets. It drove the industry to adopt IMDSv2.
What's the best defense against SSRF?
Allow-list the destinations your app is permitted to reach rather than trying to block bad ones, and validate the resolved IP (not just the URL string) against private and metadata ranges. Combine that with environment hardening: enforce IMDSv2, apply least-privilege IAM roles, and restrict outbound network egress.
Can automated tools detect SSRF?
Yes. SAST tools trace tainted input flowing into an outbound-request function, and DAST tools probe URL-accepting parameters with internal and metadata addresses to observe the response. Running both in CI catches most SSRF before deployment; SCA covers SSRF introduced through vulnerable dependencies.