Blog
3 min read

SSRF Explained: When Your Server Fetches URLs for Attackers

Server-side request forgery tricks your server into making requests to places it shouldn't — internal services, cloud metadata endpoints, localhost admin panels. Where SSRF hides (URL previews, webhooks, imports, AI agents with fetch tools), why blocklists fail, and the defences that work.

Server-side request forgery (SSRF) happens when an attacker gets your server to make a request to a URL of their choosing. Your server sits inside your network, with access the attacker doesn't have — so the attacker borrows it. SSRF is in the OWASP Top 10. (The OWASP Top 10 explained)

A simple example

A feature that fetches a URL the user provides:

app.post('/api/preview', async (req, res) => {
  const page = await fetch(req.body.url)        // ❌ any URL
  res.json({ title: extractTitle(await page.text()) })
})

Intended use: https://example.com/article. Attacker's use:

  • http://localhost:6379/ — poke your Redis.
  • http://127.0.0.1:9200/_cat/indices — read your Elasticsearch.
  • http://10.0.0.5/admin — an internal admin panel with no auth "because it's internal".
  • http://169.254.169.254/latest/meta-data/iam/security-credentials/ — the cloud metadata service, which on some cloud setups returns credentials for the server's role.

The last one has caused major real-world breaches.

Where SSRF hides

Anything where your server fetches a URL that a user influences:

  • link previews and unfurling
  • "import from URL" (images, CSV, RSS)
  • webhooks — users configure where you send events (Handling webhooks reliably)
  • PDF/screenshot generators rendering user-supplied HTML (which can load URLs)
  • image proxies and resizers (Optimizing images)
  • OAuth/OpenID discovery URLs, SAML metadata
  • AI agents with fetch or browser tools — a prompt-injected instruction can tell the agent to fetch internal URLs (Prompt injection, Egress control for AI agents)

Why blocklists fail

"Just block localhost and 127.0.0.1" doesn't work. Attackers use:

  • other representations: 127.1, 0.0.0.0, [::1], 2130706433 (decimal), 0x7f000001
  • DNS names that resolve to internal IPs (internal.attacker.com → 10.0.0.5)
  • redirects: a public URL that 302-redirects to an internal one (301 vs 302)
  • DNS rebinding: the name resolves to a public IP when you check it and an internal one when you fetch

Defences that work

1. Allow-list when you can

If you only need to fetch from known services, allow exactly those hosts. Strongest defence.

2. Resolve, then check the IP, then connect to that IP

When you must accept arbitrary URLs:

  1. Parse the URL; allow only http/https, standard ports.
  2. Resolve the hostname yourself.
  3. Reject if any resolved address is private, loopback, link-local, or otherwise internal (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, ::1, fc00::/7, fe80::/10, 0.0.0.0/8…).
  4. Connect to the IP you checked (not a fresh lookup), to defeat rebinding.
  5. Don't follow redirects automatically — or re-validate each hop.

Use a maintained library for this (e.g. SSRF-protecting HTTP agents) rather than hand-rolled regexes.

3. Isolate the fetcher at the network level

Run URL fetching in a separate service or container whose egress is restricted: no route to internal networks or metadata endpoints. Network-level controls catch what application checks miss. (Container networking internals, Linux network namespaces)

4. Protect cloud metadata

On AWS, require IMDSv2 (session tokens make simple SSRF far harder); other clouds have equivalents. Give servers the least privileged role possible. (Principle of least privilege)

5. Don't trust "internal"

Internal services should require authentication too. SSRF turns "only reachable internally" into "reachable by anyone who finds the right feature". (Mutual TLS)

6. Limit what comes back

Return only what the feature needs (a title, an image), with size and time limits. Don't echo raw responses or detailed error messages — they help attackers map your network. (Exponential backoff and timeouts)

Testing

Try your URL features with: http://127.0.0.1, http://[::1], http://169.254.169.254, a decimal IP, a domain you control pointing at 10.0.0.1, and a redirector. Ask your AI coding tool to write these as tests. (Claude Code security review)


EasySpawn runs each server in its own isolated VM, so even a fetch that slips past your checks can't reach other customers' machines — and Claude Code can add proper SSRF validation to your URL features. See how it works or join the waitlist.

Related: The OWASP Top 10 Explained · Egress Control for AI Agents · Prompt Injection in Coding Agents · Open Redirect Vulnerability

Keep reading