What Is SSRF? Server-Side Request Forgery Explained
A complete technical guide to Server-Side Request Forgery (SSRF): how it works, why it exists, how attackers exploit it, and how to detect, verify, and prevent it.
Short answer
Server-Side Request Forgery (SSRF) happens when an attacker can make your server send a request to a destination it chooses instead of one you intended. The server does the asking; the attacker picks the address.
The idea in one minute
Picture a hotel concierge who will fetch anything from outside the building for a guest — a package, a newspaper, a food order — as long as the guest gives an address. The concierge trusts that every address handed to them is a legitimate outside vendor.
Now a guest asks the concierge to walk down the hall and knock on the manager's office door instead — a door guests aren't allowed to approach directly. The concierge doesn't know the difference between "the pizza place on 5th street" and "the manager's office three doors down." An address is an address. They walk over, knock, and bring back whatever they're handed.
That's SSRF. The "concierge" is server-side code with a fetching feature — an image downloader, a webhook validator, a PDF generator, an API integration. The "manager's office" is something that was never supposed to be reachable from outside: an internal admin panel, a database, a cloud metadata endpoint. The server has legitimate access to that internal territory. The attacker doesn't. SSRF is what happens when the attacker borrows the server's legs.
How SSRF actually works
Any feature where server-side code builds a URL — even partially — from user input is a candidate. Common examples:
- "Fetch image from URL" or avatar-from-URL features
- Webhook or callback URL validators
- PDF/screenshot generators that render a given URL
- URL preview / link unfurling ("share this link" cards)
- Import-from-URL functionality (CSV, XML, RSS feeds)
- Internal microservice calls where one service passes a URL fragment to another
The server has no inherent way to know that http://192.168.0.5:8080/admin is different in kind from https://cdn.example.com/logo.png — both are just strings that resolve to a socket the server is willing to connect to. Trust boundaries live in the network topology (what's reachable), not in the URL syntax itself. If the input isn't validated against that boundary, the server will happily cross it.
The request flow
Attacker ──crafted request──▶ Vulnerable Application ──forged request──▶ Internal/Restricted Target
▲ │
└──────────────response (if visible)───┘
As a sequence:
sequenceDiagram
participant Attacker
participant App as Vulnerable Application
participant Target as Internal Target
Attacker->>App: Request with attacker-controlled URL
App->>Target: Server-initiated request to that URL
Target-->>App: Response from internal resource
App-->>Attacker: Response reflected back (or not — see "Blind SSRF")
The critical detail: the request to the internal target originates from the server, using the server's network position, IP allowlisting, and often its authentication context. The attacker never touches that network directly — they just tell the server where to go.
A minimal example
A stock-check feature that fetches product availability from a URL parameter:
GET /product/check-stock?url=https://warehouse-api.internal.example.com/stock/4471 HTTP/1.1
Host: shop.example.com
If the server does nothing but fetch whatever url contains:
GET /product/check-stock?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: shop.example.com
169.254.169.254 is the link-local address cloud providers use to serve instance metadata — including, in older configurations, temporary IAM credentials. The application dutifully fetches it and, if the response is reflected anywhere in the UI, hands the attacker cloud credentials it never meant to expose.
A vulnerable Flask snippet illustrating the root cause:
import requests
from flask import request
@app.route("/product/check-stock")
def check_stock():
target_url = request.args.get("url")
resp = requests.get(target_url) # no validation of destination
return resp.text
Why the vulnerability exists
SSRF exists because "let the server fetch a URL" and "let the server fetch any URL, including internal ones" are treated as the same feature. The root causes, almost always, are one or more of:
- No allowlist of permitted destination hosts/schemes
- Validation performed on the raw string, not the resolved destination (so
http://evil.com@internal-host/, decimal/hex IP encodings, or redirects bypass a naive blocklist) - Blocklists instead of allowlists (blocklists enumerate what you thought of; attackers enumerate what you didn't)
- No network-layer segmentation, so even a validated request from the app server can still reach sensitive internal services
- DNS resolved at request time, not validation time (DNS rebinding: the hostname resolves to a safe IP during your check, then to
127.0.0.1when the actual request fires)
What attackers look for
- Any parameter that looks like it holds a URL, hostname, or path fragment:
url=,path=,dest=,redirect=,feed=,image=,webhook=,callback= - Features described as "import from," "fetch from," "preview," "validate this URL," or "render this page"
- Cloud metadata endpoints:
169.254.169.254(AWS/Azure/GCP-style link-local metadata), and provider-specific equivalents - Internal-only ports and services: admin panels, internal APIs, databases, message queues, Kubernetes API servers, internal DNS
- Alternate URI schemes the fetching library might honor:
file://,gopher://,dict://— sometimes enabling protocol smuggling well beyond HTTP
Detection
- Static/code review: search for outbound-request functions (
requests.get,urllib,fetch,http.Client,curl_exec) fed by request parameters without a destination allowlist in between. - Dynamic/DAST: supply out-of-band (OAST) canary URLs — unique attacker-controlled domains — in every URL-shaped parameter, then watch for inbound DNS or HTTP callbacks. This catches SSRF even when the response is never reflected to the attacker.
- Response-based: look for behavioral differences (timing, response size, status code) when pointing the parameter at a reachable internal host versus an unreachable one — a classic blind-SSRF signal.
Verification: real vulnerability or false positive?
A candidate is confirmed, not assumed, when you can show one of:
- An out-of-band callback actually fired to infrastructure you control, tied to a specific request you sent
- A measurable behavioral difference between "internal host reachable" and "internal host unreachable" responses that only makes sense if the server itself made the connection
- Response content that could only have come from an internal resource (distinct banner, internal hostname, non-public data)
A parameter merely accepting a URL-shaped string is not evidence of SSRF — plenty of applications validate destinations correctly before fetching. Confirm the request actually reached where you told it to, from the server, before calling it a finding.
Real-world impact
SSRF is rarely the end goal — it's a pivot. Consequences depend entirely on what's reachable from the vulnerable server:
- Cloud credential theft: reaching a cloud metadata service to steal temporary IAM credentials, escalating a web bug into full cloud-account compromise. This exact chain — an SSRF-capable request path used to reach AWS instance metadata — was the mechanism behind the widely reported 2019 Capital One breach.
- Internal network mapping: using response timing or errors to enumerate which internal hosts and ports exist, effectively port-scanning through the vulnerable server.
- Access to internal-only services: admin interfaces, internal APIs, or databases that were "secured" only by not being internet-facing.
- Chaining into RCE: if an internal service trusts unauthenticated requests from the app server (a common assumption), SSRF can be the first step toward remote code execution.
Prevention
- Allowlist, don't blocklist: only permit specific, known-safe destination hosts/schemes. Reject everything else by default.
- Validate the resolved destination, not just the string: parse the URL, resolve DNS, and check the resulting IP against denied ranges (private/link-local/loopback, including IPv6) at connection time — not just at input time — to close DNS-rebinding gaps.
- Disable unneeded URL schemes: if the feature only needs
https://, don't let the HTTP client honorfile://,gopher://, ordict://. - Segment the network: even a "validated" fetch shouldn't be able to reach sensitive internal services if network policy blocks it outright. Defense in depth, not a single check.
- Don't follow redirects blindly: a validated URL can redirect to an internal one. Validate the destination at every hop, or disable automatic redirect-following for this class of request.
- Harden cloud metadata access: enforce IMDSv2-style token requirements (or provider equivalents) that plain SSRF can't satisfy with a simple GET request.
- Use an egress proxy for outbound fetches: centralize and log all server-initiated outbound requests through infrastructure that enforces the allowlist, rather than trusting each feature to reimplement validation correctly.
Related vulnerabilities
- XXE — XML parsers can be coerced into making external requests, functioning as an SSRF vector through a different door
- Open redirect — can be chained to defeat naive SSRF allowlists that only check the first hop
- CRLF injection — can be combined with SSRF to smuggle additional requests through a forged connection
- Cloud metadata attacks — the specific, high-value target class SSRF most often escalates into
Testing methodology (do this safely)
- Only test targets you're authorized to test — your own infrastructure, a lab environment, or a program that explicitly permits SSRF testing in scope.
- Use OAST/canary domains you control to confirm out-of-band interactions rather than guessing from indirect signals.
- For blind SSRF, rely on timing and DNS/HTTP callback evidence — don't assume exploitation from a single ambiguous response.
- Never pivot from a confirmed SSRF into real internal systems or data outside an authorized engagement. Proving reachability is enough; exfiltrating real internal data is not part of a responsible test.
- Document the exact request, the destination reached, and the evidence of reachability — this is what turns a suspicion into a verifiable finding.
Further reading
- OWASP: Server-Side Request Forgery
- OWASP Cheat Sheet Series: SSRF Prevention Cheat Sheet
- PortSwigger Web Security Academy: Server-side request forgery (SSRF)
- MITRE: CWE-918 — Server-Side Request Forgery (SSRF)
Nyxeara perspective
Automated detection of SSRF is fundamentally an evidence problem: a scanner that only checks whether a parameter accepts a URL-shaped value will drown in false positives. Reliable detection requires out-of-band confirmation — a callback that proves the server actually made the request — before a finding is reported as real rather than suspected.