SSRF tricks the server into making HTTP requests to internal resources, exposing cloud credentials, internal services, and sensitive data.
TL;DR
169.254.169.254 returns IAM credentials in two HTTP requests — no authentication required on IMDSv1Server-Side Request Forgery (CWE-918, OWASP A10:2021) occurs when an attacker can cause a server to make HTTP requests to an arbitrary destination — internal or external. The server acts as a proxy, so requests originate from a trusted internal IP. Firewalls, security groups, and VPC routing rules that prevent external clients from reaching internal services do not block requests from the server itself.
SSRF earned its own OWASP Top 10 category in 2021 — the first new entry not seen in A09:2017. The reason: cloud-first architectures made every EC2 instance, Lambda function, GCE VM, and Azure Virtual Machine expose a link-local metadata endpoint (169.254.169.254) that is only reachable from within the instance. A single SSRF vulnerability on any application running in the cloud becomes a direct credential theft path.
SSRF differs from CSRF in a key way: CSRF targets the victim's browser and their session; SSRF targets the server and its network position. SSRF is typically more impactful because internal services often have no authentication, assuming network isolation is sufficient. OWASP A10:2021 references 385 CVEs mapped to CWE-918 and notes that while incidence rates appear low in testing, above-average exploit/impact potential puts it in the Top 10.
The attacker identifies a parameter whose value is used in a server-side HTTP request. This could be a URL field, a webhook callback, an image upload-from-URL endpoint, or an import/fetch feature. Instead of submitting a legitimate URL, the attacker provides an internal address. The server makes the request from its own context and, in the simplest case, returns the response directly.
The critical difference from a user-side redirect: the application's outbound request bypasses all external-facing controls. A WAF inspecting inbound traffic does not inspect the application's own outbound requests to the metadata service.
Six distinct SSRF classes cover the full attack surface:
| Variant | Mechanism | Primary Target | Typical CVSS |
|---|---|---|---|
| Basic In-Band | Server returns response to attacker | Internal services, cloud metadata | 7.5 |
| Blind OOB | No response returned; OOB callback confirms | Backend services, second-order chains | 7.5 |
| Cloud Metadata | Targets 169.254.169.254 or equivalent | AWS/GCP/Azure IAM credentials | 8.5–9.0 |
| Protocol Smuggling | Non-HTTP schemes (gopher://, file://, dict://) | Redis RCE, filesystem reads, MySQL | 9.0+ |
| Header-Based | Injected headers cause server-side requests | Internal routing, metadata bypass | 8.0–9.2 |
| DNS Rebinding | TOCTOU: valid IP at validation → internal IP at request | Bypasses allowlist filters | 7.5 |
The server fetches a URL supplied by the attacker and returns the response body directly. Any endpoint accepting a URL parameter and returning its content is a candidate. Attack surfaces include link preview endpoints, profile avatar upload-from-URL, import-from-feed functionality, and webhook test buttons that display the response.
The server makes the outbound request but suppresses the response. Confirmation requires an external OOB listener (Burp Collaborator, Interactsh, oast.fun). The BreachVex scanner uses a graduated confidence model: a DNS-only callback is treated as potential, an HTTP callback as a confirmed high-severity finding, and data exfiltration via the out-of-band body as confirmed critical. DNS-only alone never promotes to confirmed — infrastructure components routinely resolve hostnames without making HTTP requests.
Every major cloud provider exposes a link-local metadata service at 169.254.169.254 (AWS, GCP, Azure, DigitalOcean) or equivalent addresses. On AWS with IMDSv1, two requests suffice to obtain temporary IAM credentials — no token, no authentication, no prior knowledge of the role name required. The 2019 Capital One breach followed exactly this pattern, exposing 106 million records.
When the URL-fetching library supports schemes beyond http:// and https://, alternative protocols enable raw TCP communication with internal services. gopher:// allows injecting arbitrary bytes into a Redis connection on port 6379, writing a cron job for RCE. file:///etc/passwd reads the local filesystem. dict:// queries Memcached. These attacks do not require HTTP — the server's URL library issues the raw protocol directly.
HTTP headers consumed server-side can trigger outbound requests. The X-Forwarded-Host header is processed by Angular SSR, Astro, Symfony, and Django to construct internal API URLs. Injecting X-Forwarded-Host: attacker.com causes the framework to make an HTTP request to http://attacker.com/api/config rather than the legitimate internal endpoint. CVE-2026-27739 (CVSS 9.2) demonstrates this pattern on Angular SSR.
Capital One 2019 — A misconfigured WAF running on EC2 had SSRF. The attacker queried http://169.254.169.254/latest/meta-data/iam/security-credentials/ISRM-WAF-Role, obtained temporary AWS credentials, enumerated 700+ S3 buckets, and exfiltrated 30 GB of data. Impact: 106 million records, $80M OCC fine, $190M class action settlement. Root cause: IMDSv1 (no token required) and an overprivileged IAM role.
CVE-2025-6454 — GitLab CE/EE (CVSS 8.5) — The webhook custom header feature introduced in GitLab 16.11 allowed CRLF injection into header names. An authenticated user with Developer+ role could inject \r\n sequences to forge arbitrary HTTP headers in outbound webhook requests, enabling access to internal proxies and metadata services. Fixed in 18.1.6, 18.2.6, 18.3.2.
CVE-2025-68437 — Craft CMS GraphQL — The saveAssets mutation's _file { url } parameter fetched content from arbitrary URLs without validation. An attacker with GraphQL asset permissions could supply http://169.254.169.254/latest/meta-data/iam/security-credentials/ — the fetched credentials were saved as an asset and retrievable via the CMS API.
CVE-2022-22947 — Spring Cloud Gateway (CVSS 10.0) — SpEL expressions in route predicates created an SSRF→RCE chain. Exploited in the wild within 72 hours of public disclosure, before most organizations could patch.
url, callback, webhook, endpoint, redirect, image, src), HTTP headers (Host, X-Forwarded-Host, Referer), and data import/export endpoints.https://<burp-collaborator-id>.oastify.com as the parameter value for each candidate.http://127.0.0.1/, http://169.254.169.254/latest/meta-data/, http://localhost:3000/, http://localhost:9200/.http://2130706433/), IPv6 (http://[::1]/), and redirect chain (https://302.r3dir.me/?r=http://169.254.169.254/).Interactsh (ProjectDiscovery) provides a self-hosted OOB listener. A DNS query without HTTP follow-up suggests SSRF with HTTP egress blocked — still exploitable for internal port scanning via timing. SSRFmap automates parameter fuzzing. Nuclei has SSRF templates covering cloud metadata endpoints and known vulnerable parameter names from a corpus of 80+ canonical names (url, webhook, callback, image_url, avatar_url, redirect_uri, etc.).
BreachVex detects SSRF through multiple complementary techniques: per-parameter out-of-band callback injection with a graduated confidence model, differential response analysis against RFC 5737 control IPs, and direct probing of common internal services — cloud metadata endpoints, container and orchestration APIs, monitoring dashboards, and internal datastores — using marker-based confirmation.
Blocklists are bypassed by encoding, DNS aliases, and IPv6 mapping. Only allowlists work:
import ipaddress
import socket
import urllib.parse
ALLOWED_HOSTS = frozenset({"api.stripe.com", "hooks.slack.com"})
def validate_url(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
# 1. Scheme allowlist — blocks gopher://, file://, dict://, ftp://
if parsed.scheme not in ("http", "https"):
return False
host = (parsed.hostname or "").rstrip(".") # strip trailing FQDN dot (CVE-2025-62718 class)
if not host or host not in ALLOWED_HOSTS:
return False
# 2. Resolve DNS and validate the resulting IP — defeats DNS aliases and nip.io
try:
results = socket.getaddrinfo(host, None, proto=socket.IPPROTO_TCP)
for (_, _, _, _, sockaddr) in results:
ip = ipaddress.ip_address(sockaddr[0])
# Normalize IPv6-mapped IPv4 (::ffff:127.0.0.1 → 127.0.0.1)
if isinstance(ip, ipaddress.IPv6Address) and ip.ipv4_mapped:
ip = ip.ipv4_mapped
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
return False
except (socket.gaierror, ValueError):
return False # fail-closed on resolution errors
return TrueEnforce IMDSv2 at the infrastructure level — application-level fixes are insufficient if the metadata endpoint is accessible:
# Per instance
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1
# Verify compliance across account
aws ec2 describe-instances \
--query "Reservations[].Instances[?MetadataOptions.HttpTokens=='optional'].{ID:InstanceId}" \
--output table
# Organization-wide SCP — block launching instances with IMDSv1
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Condition": {
"StringEquals": { "ec2:MetadataHttpTokens": "optional" }
}
}Application-level validation should be a last resort, not the only control. Restrict egress at the network layer:
169.254.169.254/32 and 168.63.129.16/32 in security group outbound rulesCAP_NET_RAW in container security contextsNever rely on a blocklist of "bad" IPs or hostnames. Attackers bypass them using decimal dword encoding (2852039166 for 169.254.169.254), IPv6-mapped addresses ([::ffff:a9fe:a9fe]), redirect chains (302.r3dir.me), and DNS rebinding. An allowlist of permitted destinations is the only control that survives these bypass techniques.
| SSRF | CSRF | |
|---|---|---|
| Who makes the request | Server | Victim's browser |
| Whose session is used | Server's network identity | Victim's authenticated session |
| Primary target | Internal network, cloud metadata | Actions on authenticated endpoints |
| Impact | Credential theft, RCE, data exfiltration | Unauthorized state changes as victim |
| Requires victim interaction | No | Yes (victim must load attacker page) |
| Defense | URL allowlist + egress filtering | SameSite cookies + CSRF tokens |
SSRF (CWE-918) is a web vulnerability where an attacker causes a server to make HTTP requests to an arbitrary destination. The server acts as a proxy from a trusted IP, bypassing firewalls and network controls that block external access to internal resources.
CSRF (Cross-Site Request Forgery) tricks a victim's browser into sending a request using their session — the attack targets the user. SSRF tricks the server itself into making requests to internal systems — the attack targets the infrastructure. SSRF impacts are typically more severe because internal services are fully reachable from the server.
SSRF was elevated to A10:2021 because cloud deployments made every EC2, Lambda, GCE, and Azure VM instance expose a link-local metadata endpoint by default. SSRF attacks surged 452% from 2023 to 2024 (SonicWall 2025 Cyber Threat Report), driven by AI-assisted tooling and cloud-first architectures.
In 2019, a misconfigured WAF running on EC2 had an SSRF vulnerability. An attacker queried the AWS IMDSv1 endpoint at 169.254.169.254, obtained temporary IAM credentials for the overprivileged role ISRM-WAF-Role, enumerated 700+ S3 buckets, and exfiltrated 30 GB of data. The breach exposed 106 million customer records and resulted in an $80M OCC fine.
Yes. The most direct path is the gopher:// protocol targeting an internal Redis instance on port 6379. gopher:// allows raw TCP communication, enabling an attacker to inject Redis commands that write a cron job or SSH authorized_keys file — achieving RCE. CVE-2022-22947 (Spring Cloud Gateway, CVSS 10.0) combined SSRF with SpEL injection to achieve full RCE, exploited in the wild within 72 hours of disclosure.
In blind SSRF, the server makes the outbound request but does not return the response in the HTTP reply. Detection requires out-of-band (OOB) techniques: an attacker-controlled callback server (Burp Collaborator, Interactsh) records DNS lookups or HTTP hits confirming the server reached the target URL.
Blocklist bypasses include: IP encoding (decimal dword 2130706433 instead of 127.0.0.1), octal/hex notation, IPv6-mapped addresses (::ffff:127.0.0.1), DNS aliases (localtest.me, nip.io), redirect chains (302.r3dir.me), and URL parser differentials (user-info@169.254.169.254 confusion). Allowlists + post-DNS-resolution IP validation is the only reliable defense.
IMDSv2 requires a PUT request with a TTL header to obtain a session token before any metadata can be retrieved. It significantly raises the bar for basic SSRF (which only issues GET). However, it is bypassable when the SSRF allows custom HTTP headers, multi-step requests, or when running in a headless browser (PDF generators, Puppeteer). As of late 2024, only 32% of EC2 instances enforced IMDSv2.
Manual testing uses Burp Suite Pro with Burp Collaborator for OOB detection. Automated tools include Interactsh (self-hosted OOB listener), SSRFmap (automated parameter fuzzer), and Gopherus (gopher:// payload generator for Redis/FastCGI/MySQL chains). Nuclei has SSRF templates for common patterns.
Use an allowlist of permitted domains, validate the scheme (https only), resolve DNS and block any private/link-local/loopback IP after resolution, disable redirect following, and enforce connection to the resolved IP directly (DNS pinning) to prevent rebinding attacks. Never use blocklists alone — encoding bypasses are trivial.