Server-side request forgery (SSRF)
CWE-918OWASP A10:2021Updated August 29, 20266 min read
Server-side request forgery (SSRF) is a vulnerability that lets an attacker force your server to make requests to addresses of their choosing. This gives them a path to internal systems and cloud services that were never meant to be reachable from the internet.
Server-side request forgery, or SSRF, is one of the most insidious web vulnerabilities: the attacker does not drive your browser, but your own server. This article explains what SSRF is, how such an attack unfolds in practice, what an attacker can achieve with it, and how to lock your application down against it.
What is server-side request forgery?
Server-side request forgery (SSRF) is a vulnerability that lets an attacker persuade your server to open connections, on their behalf, to an address the attacker chooses. The application believes it is making a legitimate request, but in reality it acts as a relay to places the attacker could never have reached directly.
Think of a receptionist who dials any phone number a visitor writes on a slip of paper. The visitor is stuck outside and cannot enter the building, but the receptionist sits inside, and can therefore also ring internal extensions. If the visitor asks for “extension 1234, the vault room”, the receptionist dutifully dials it, because after all it is written on the slip. SSRF abuses exactly that trusted position of the server inside the network.
How does an SSRF attack work?
SSRF appears wherever an application takes a URL or address from user input and then fetches it itself: a webhook, an “import from URL” feature, a link preview, a PDF generator or an image proxy. As long as that input is not checked, the user decides which address the server connects to.
Vulnerable:
// Image proxy that blindly fetches any address it is given
app.get('/fetch', async (req, res) => {
const url = req.query.url;
const response = await fetch(url);
const body = await response.text();
res.send(body);
});
This proxy fetches any address placed in the query parameter. If an attacker requests /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/, the server queries the cloud provider’s metadata endpoint and returns the temporary access keys in the response. An address like http://localhost:6379 opens the internal Redis database just as easily, and file:///etc/passwd reads local files on demand.
Safe:
import dns from 'node:dns/promises';
const ALLOWED_HOSTS = new Set(['images.example.com', 'cdn.example.com']);
function isPrivate(ip) {
return /^(10\.|127\.|169\.254\.|192\.168\.|::1|fc00:|fe80:)/.test(ip)
|| /^172\.(1[6-9]|2\d|3[01])\./.test(ip);
}
app.get('/fetch', async (req, res) => {
let target;
try { target = new URL(req.query.url); }
catch { return res.status(400).send('Invalid URL'); }
if (target.protocol !== 'https:') return res.status(400).send('Only https');
if (!ALLOWED_HOSTS.has(target.hostname)) return res.status(403).send('Host not allowed');
const { address } = await dns.lookup(target.hostname);
if (isPrivate(address)) return res.status(403).send('Blocked');
const response = await fetch(target, { redirect: 'error' });
res.send(await response.text());
});
The safe version closes three taps at once. First the input is parsed as a real URL and restricted to https. Next the hostname must appear on an explicit allowlist: only what you approve in advance gets through. Finally the hostname is resolved to an IP address and checked against private and link-local ranges, so an approved name that secretly points to 127.0.0.1 or 169.254.169.254 still fails. Refusing redirects stops an external server from bouncing you back inside on a second hop.
What is the impact of SSRF?
The severity of SSRF varies widely, which explains the span from medium to critical. In an isolated environment with no interesting internal services, it may stop at port scanning. But in a typical cloud setup the prize is large: the metadata endpoint (169.254.169.254) hands out temporary credentials, and with those keys an attacker can often take over the entire cloud environment.
What makes that endpoint so rewarding is its design. In the first version, IMDSv1, a plain GET request is enough: no header, no authentication, which is precisely the one capability SSRF grants. IMDSv2 instead requires a PUT request first for a short-lived token that every further query must carry in a header; Azure and Google Cloud likewise demand an extra header. How much a stolen key is worth is decided by the instance role: if it may read object storage or fetch secrets, so may the attacker. And because such credentials usually stay valid for hours, they are exfiltrated once and reused from the attacker’s own infrastructure, outside your network monitoring.
Technically, SSRF opens the door to everything the server can reach internally: admin panels with no external exposure, internal APIs, databases, the Kubernetes API or a message queue. The attacker can map the internal network, exfiltrate sensitive data, and sometimes escalate an SSRF into full remote code execution. For the business this means data breaches, cloud services abused on your account, and an attacker moving laterally through the infrastructure from a single weak endpoint. A well-known example is the 2019 Capital One breach, where an SSRF attack reached the AWS metadata service and exposed the data of more than a hundred million customers.
How do you detect SSRF?
During a manual test, a pentester enters an address pointing to a server under their own control into every field that looks like it accepts a URL or host, then watches for the callback. If a DNS or HTTP request arrives, the application really is fetching the address. Even without a visible response, so-called blind SSRF, that outbound request gives the vulnerability away. Testers also probe metadata endpoints, alternative IP notations and redirects to defeat filters.
To keep callbacks attributable, a tester assigns a unique subdomain to every field under test; a lookup for field7.test.example.net then shows at once which input produced it. It also matters whether only a DNS query lands or a full HTTP connection follows, since a bare name resolution may come from a proxy or a security appliance. When no callback appears at all, side channels take over: a closed internal port is refused instantly, a filtered one runs into the timeout, an open one answers quickly. Those timings and differing status codes map the internal network even though the application reveals nothing.
Where you look matters as much as how, because not every vulnerable input is called url. Target addresses hide in webhook configuration, in XML documents with an external entity, in uploaded SVG files a converter renders server side, and in headers such as Referer. The fetch is often deferred to a background job, so a listener shut down too early misses exactly those cases.
Automated scanners flag suspicious URL parameters but often miss precisely the blind cases. AssistSec includes SSRF as standard in a penetration test and deliberately checks for those blind variants.
How do you prevent SSRF?
A single measure is rarely enough. What works is a chain: strict validation in the application, a tightly cut network, and a cloud configuration that gives away as little as possible.
- Where you can, do not accept a free-form URL at all. Let the user pick from destinations registered in advance and pass only their identifier.
- Use an allowlist of permitted hosts, domains and protocols; reject everything else. Validate the parsed URL rather than the raw string, and refuse embedded credentials and unusual ports.
- Allow only
httpandhttpsand block schemes such asfile,gopher,ftpanddict. - Resolve the hostname to an IP address and block private, loopback and link-local ranges, including IPv6 and 169.254.169.254.
- Then connect to that exact validated IP address and carry the original hostname only in the Host header and SNI, which closes the window that DNS rebinding relies on.
- Forbid redirects, or re-validate every redirect against the same rules, capping the number of hops and treating each as fresh, untrusted input.
- Route outbound traffic through a forward proxy that enforces the allowlist centrally, so every new feature need not reimplement them.
- Run the fetch functionality with minimal privileges in a separate, isolated network segment.
- Disable cloud metadata or enforce IMDSv2 with a token and a hop limit of 1, so a single plain GET request cannot leak keys, and give each workload its own least-privilege role.
- Do not return the fetched content unfiltered: cap the size and the accepted content type, and answer uniformly on every error, so neither status code nor response time reveals anything internal.
- Monitor your outbound traffic for unexpected destinations and alert on connection attempts to loopback, link-local and private ranges.
Sources
Frequently asked questions
What is the difference between SSRF and CSRF?
In CSRF an attacker abuses a logged-in user's browser to perform unwanted actions. In SSRF they abuse the server itself to make requests to internal addresses. CSRF happens on the client side, SSRF on the server side.
Is blind SSRF dangerous if I get no response back?
Yes. Even without a visible response an attacker can scan internal ports, trigger services that act on a request, or use the flaw as a stepping stone to further attacks. The outbound DNS or HTTP request itself gives the vulnerability away.
Does a firewall protect against SSRF?
Not on its own. The server makes the request from inside the network, often from behind the firewall. Segmentation limits the damage, but input validation in the application is the real defence.
Why isn't a blocklist enough against SSRF?
Blocklists are almost always bypassable with alternative IP notations, IPv6 addresses, redirects, or DNS that changes value after your check (DNS rebinding). An allowlist plus IP validation is far more reliable.
Related articles
- VulnerabilitiesCWE-352A01:2021Cross-site request forgery (CSRF)Cross-site request forgery (CSRF) explained: how an attacker abuses a logged-in user's browser to perform unwanted actions, and how you prevent it.
- VulnerabilitiesCWE-94A03:2021Remote code execution (RCE)Remote code execution (RCE) explained: how attackers run their own commands or code on your server through unvalidated input, and how to prevent it.
- VulnerabilitiesCWE-16A05:2021Security misconfigurationSecurity misconfiguration explained: how default passwords, debug modes and open cloud buckets let attackers in, and how to harden your systems.
- VulnerabilitiesCWE-611A05:2021XML external entity injection (XXE)XML external entity injection (XXE) explained: how attackers abuse an XML parser to read files and reach internal systems, and how to prevent it.