A10 SSRF
A10 is Server-Side Request Forgery (SSRF): an attacker tricks your server into fetching arbitrary URLs. Cloud metadata services, internal admin panels, and even databases become reachable from your “just an image proxy” endpoint.
SSRF anatomy + defence in depth
EXAMPLE
// === THE VULNERABLE PATTERN ===
// User-controlled URL fetched server-side
app.get('/proxy/avatar', async (req, res) => {
const r = await fetch(req.query.url); // ← attacker controls
res.type('image').send(Buffer.from(await r.arrayBuffer()));
});
// Attacker requests:
// /proxy/avatar?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
// → leaks the EC2 instance role credentials
//
// /proxy/avatar?url=http://localhost:8500/v1/kv/?recurse
// → exfiltrates the Consul KV store
// === DEFENCES (apply ALL of these) ===
// 1) Allow-list outbound destinations (best defence)
const ALLOWED_HOSTS = new Set(['avatars.example.com', 'cdn.example.com']);
function safeUrl(input) {
const u = new URL(input);
if (u.protocol !== 'https:') throw new Error('https only');
if (!ALLOWED_HOSTS.has(u.hostname)) throw new Error('host not allowed');
return u;
}
// 2) Block RFC1918 / link-local / loopback at the network LEVEL
import ipaddr from 'ipaddr.js';
import { promises as dns } from 'node:dns';
async function isPublic(hostname) {
const addrs = await dns.lookup(hostname, { all: true });
for (const { address } of addrs) {
const ip = ipaddr.parse(address);
const range = ip.range();
if (['private','loopback','linkLocal','uniqueLocal','carrierGradeNat'].includes(range)) {
return false;
}
// Block AWS / Azure / GCP metadata services
if (address === '169.254.169.254' || address === 'fd00:ec2::254') return false;
}
return true;
}
// 3) Don't follow redirects to internal IPs
await fetch(url, { redirect: 'manual' }); // verify Location header yourself
// 4) Use IMDSv2 — even if SSRF works, attacker needs a 6-hour token + a PUT
// Set the launch config / instance attribute:
// HttpTokens=required, HttpPutResponseHopLimit=1
// 5) Egress firewall at the VPC level
// Default-deny outbound; explicit allow-list per service.
// This stops SSRF even when application defences fail.
// 6) URL parser strictness — the SSRF gotcha
new URL('http://[::1]/').hostname // '::1' loopback
new URL('http://2130706433/').hostname // '2130706433' = 127.0.0.1
new URL('http://0/').hostname // '0' → 0.0.0.0 → loopback on Linux
new URL('http://[email protected]/').hostname // 'evil.com' ← user info trick
// Always resolve to IP and validate THE IP, not the input string.
Why it matters
IMDSv2 is the single biggest mitigation if you’re on AWS. It costs nothing, takes 5 minutes per instance, and turns most SSRF exposures from “steal cloud credentials” into “harmless 404”.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// A10 SSRF — your server fetches an attacker-controlled URL, // pivoting to cloud metadata (169.254.169.254) or internal services. // Fix: allow-list outbound domains, deny RFC1918 + metadata IPs, use IMDSv2.Try it Yourself »
Exercise
OWASP Top 10 (2021) category #10 short acronym.
Four letters.
Discussion
Loading…