Server-Side Request Forgery (SSRF) and How to Block It
Many features fetch a URL on the user's behalf: link previews, "import from URL", webhooks, PDF generators. If an attacker controls the URL, they can make your server request internal addresses it can reach but they can't. That's server-side request forgery.
What attackers aim for
- Cloud metadata endpoints (for example
http://169.254.169.254/) that can expose temporary cloud credentials. - Internal admin panels, databases and services on private networks.
localhostservices that assume local callers are trusted.
The vulnerable code
app.post('/api/preview', async (req, res) => {
const page = await fetch(req.body.url); // any URL, including internal ones
res.json(extractPreview(await page.text()));
});
Layered defences
1. Validate the URL and resolved IP. Allow only http/https, resolve the hostname, and reject private, loopback, link-local and other reserved ranges. Check the IP you actually connect to, not just the hostname, or DNS rebinding can bypass you:
import { lookup } from 'node:dns/promises';
import ipaddr from 'ipaddr.js';
async function assertPublicUrl(raw) {
const url = new URL(raw);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('Unsupported protocol');
const addresses = await lookup(url.hostname, { all: true });
for (const { address } of addresses) {
if (ipaddr.process(address).range() !== 'unicast') throw new Error('Address not allowed'); // process() unwraps IPv4-mapped IPv6
}
return { url, addresses }; // connect to these exact addresses
}
2. Don't follow redirects blindly. A public URL can redirect to an internal one. Disable automatic redirects or re-validate each hop.
3. Isolate the fetcher. Run URL-fetching in a separate service or egress proxy that has no route to internal networks.
4. Harden cloud metadata. On AWS, require IMDSv2 (session tokens) and set a hop limit of 1 for containers; other clouds require special headers on metadata requests.
5. Prefer allow-lists. If users only need to reach known domains (for example their own GitHub repos), allow those and nothing else.
Also limit the response
Set timeouts and maximum response sizes, and never return raw responses or error details from internal fetches to the user.
Comments
No comments yet. Have a question or a tip? Be the first to share it.