Server-Side Request Forgery (SSRF)
Block web apps from fetching attacker-controlled URLs and reaching internal services or cloud metadata.
URL: trusteed.io/academy/web-security/server-side-request-forgery
Server-Side Request Forgery (SSRF) occurs when a web application fetches a remote resource based on a URL supplied by the client without properly validating or restricting the destination — allowing an attacker to make the server issue requests to internal systems, cloud metadata services, or otherwise unreachable infrastructure, using the server's own network position. SSRF was added to the OWASP Top 10 as its own category in the 2021 revision after a string of high-profile breaches demonstrated how directly it can escalate to full infrastructure compromise.
What Is SSRF, in a Web Application Context?
Any web application feature that fetches a URL server-side on the user's behalf is a potential SSRF vector: importing an image from a link, generating a PDF or screenshot preview of a submitted URL, validating a webhook callback URL, or fetching an RSS/Atom feed a user configured. Because the request originates from the server rather than the attacker's own machine, it can reach destinations the attacker could never access directly — internal admin interfaces not exposed to the internet, other services on the internal network, and, in cloud environments, the instance metadata endpoint that can return temporary cloud credentials if reached.
Why AI Coding Assistants Get This Wrong
"Fetch this URL server-side and use the result" reads as a simple, common feature request, and passing the client-supplied URL directly to an HTTP client is the shortest correct-looking implementation — it works in every normal test a developer runs. The vulnerability only appears when someone supplies a URL pointing somewhere unexpected, which functional testing rarely exercises, and redirect-following (the default behavior of most HTTP clients) means even an initially-validated URL can end up somewhere unvalidated after a redirect.
Vulnerable Pattern
// A "generate PDF preview" feature — fetches whatever URL the user submits
app.post('/api/generate-preview', authenticate, async (req, res) => {
const html = await axios.get(req.body.url); // no validation of the destination at all
const pdf = await renderToPdf(html.data);
res.send(pdf);
});
Secure Pattern
const dns = require('dns').promises;
const ALLOWED_HOSTS = ['blog.trusted-source.com', 'docs.trusted-source.com'];
function isPrivateIp(ip) {
return /^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[0-1])\.|169\.254\.169\.254)/.test(ip) || ip === '::1';
}
async function validateUrl(rawUrl) {
const url = new URL(rawUrl);
if (url.protocol !== 'https:') throw new Error('Only https allowed');
if (!ALLOWED_HOSTS.includes(url.hostname)) throw new Error('Host not allowed');
const addresses = await dns.resolve4(url.hostname);
if (addresses.some(isPrivateIp)) throw new Error('Resolves to a private address');
return url;
}
app.post('/api/generate-preview', authenticate, async (req, res) => {
try {
const url = await validateUrl(req.body.url);
const html = await axios.get(url.toString(), { maxRedirects: 0, timeout: 5000 });
const pdf = await renderToPdf(html.data);
res.send(pdf);
} catch (err) {
res.status(400).json({ error: 'Invalid or disallowed URL' });
}
});
This is the same core defense pattern regardless of whether the vulnerable feature sits behind a formal API or directly inside a traditional web application — allow-list the destination, resolve and check the actual IP, disable redirect-following, and restrict the scheme.
Copy-Paste Rules for Cursor, Claude Code, and Codex
---
description: SSRF prevention for web applications — validating server-side outbound fetches
alwaysApply: true
---
# SSRF Prevention Rules (Web Applications)
- Any feature that fetches a URL server-side (link previews, image import,
PDF generation from a URL, webhook validation) must validate the
destination against an explicit allow-list — never fetch an arbitrary,
client-supplied URL unrestricted.
- Resolve the hostname to an IP address before connecting, and reject
private/reserved IP ranges and the cloud metadata address
(169.254.169.254 and its IPv6 equivalent).
- Disable automatic redirect-following on server-side fetches, or
re-validate the redirect target with the same checks before following it.
- Restrict the allowed URL scheme (typically `https:` only), and set an
explicit timeout on every server-side outbound request.
- When generating a feature that renders, previews, or imports content
from a user-supplied URL, implement URL validation in the same pass as
the fetch logic — never as a follow-up hardening step.
Verification Checklist
- Every server-side URL-fetching feature validates against an explicit allow-list
- Resolved IP addresses are checked against private/reserved ranges before connecting
- Cloud metadata addresses are explicitly blocked
- Redirects are disabled or re-validated on server-side fetches
- Timeouts are set on all outbound requests
- SSRF payloads are included in ongoing security testing — see Trusteed's vulnerability scanner for continuous SSRF testing
Frequently asked questions
Why is SSRF specifically dangerous in cloud-hosted applications?
Cloud instance metadata services can return temporary credentials for the hosting service's IAM role if an SSRF vulnerability allows a request to reach them — turning a request-forgery bug into full cloud account compromise, which is the chain seen in several major cloud security incidents.
Is a URL format validation check (regex matching "looks like a URL") sufficient protection?
No. Format validation confirms the input is a syntactically valid URL but says nothing about where it actually points — the critical check is validating the resolved IP address against private and reserved ranges, not the string's surface appearance.
Do webhook receiver validation checks need the same SSRF protection?
Yes — any server-side process that fetches a URL to validate a webhook endpoint (a common "ping the URL to confirm it's real" pattern) needs the same allow-list and IP validation, since it's functionally identical to any other server-side fetch feature from an SSRF perspective.