iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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.

Test yourself

Q1. SSRF stands for…
Q2. On AWS, SSRF often pivots to…
Q3. A solid mitigation is…

Discussion

Loading…