Skip to content
العربية

SSRF (Server-Side Request Forgery)

How making the server fetch a URL you choose lets you reach internal systems — and why blacklists of addresses never hold.

Estimated time
25 min
Level
Intermediate
Your progress in this track0%

Before this lesson

SSRF is when a server fetches a URL that you control. Lots of features do this: a link preview, an avatar imported from a URL, a webhook, a PDF generator that loads images. The request leaves from the server, inside the server’s network — so you can reach things your own browser never could: internal services, admin panels, and the cloud metadata endpoint that hands out the server’s credentials.

What is the vulnerability?

preview.js
app.post('/fetch', async (req, res) => {
  const r = await fetch(req.body.url); // whatever URL the user sent
  res.send(await r.text());
});

You send https://example.com and it previews a normal page. You send http://127.0.0.1/admin and the server fetches its own internal admin panel and hands you the response. The server is a confused deputy: it has access you do not, and it is using that access on your behalf.

Why does it happen?

The server treats a user-supplied destination as trusted. It never asks “should I be allowed to fetch this?”. The dangerous targets are the ones only reachable from inside: loopback (127.0.0.1), private ranges (10.x, 192.168.x, 172.16–31.x), and the cloud metadata service at 169.254.169.254, which returns temporary credentials for the server’s role.

Why blacklists fail

The instinct is to block localhost and 127.0.0.1. But one address has many spellings:

all of these are 127.0.0.1
127.0.0.1   127.1   2130706433   0x7f000001   0177.0.0.1   [::1]   localhost

A text filter that bans one spelling misses the rest. Even a filter that resolves the address and blocks private ranges can Relying on listing every bad case; it fails the moment one case is forgotten — here, the link-local metadata range. — the link-local 169.254.0.0/16 where the metadata service lives. You walk through both gaps in the medium and hard labs.

How to prevent it

safe.js
const ALLOW = new Set(['https://images.cdn.example', 'https://docs.example']);
app.post('/fetch', async (req, res) => {
  const url = new URL(req.body.url);
  if (!ALLOW.has(url.origin)) return res.status(400).end();   // allowlist, not blacklist
  const r = await fetch(url, { redirect: 'error' });          // no redirect to internal
  res.send(await r.text());
});

Allowlist the destinations the feature actually needs — do not try to list every bad address. Resolve the hostname and reject private, loopback and link-local IPs (check after resolution, and again after any redirect). Isolate the fetching service in a network segment that cannot reach internal systems, require IMDSv2 for cloud metadata, and give the server the least privilege possible so leaked credentials do limited harm.

Review questionAn SSRF filter blocks any URL whose text contains '127.0.0.1'. How is it bypassed?

Labs

  • The server fetches any URL

    EasyBrowser lab

    Make the server fetch its internal admin panel on its local address and get the master stash code.

  • A filter that matches text

    MediumBrowser lab

    Reach the internal admin panel again, bypassing the text filter.

  • The address everyone forgets to block

    HardBrowser lab

    Steal the Ravens’ cloud access keys from the metadata service, through the server.