SSRF: when a request decides where your server connects

Server-side request forgery turns your server into a proxy. If request input chooses the URL your code fetches, an attacker can reach whatever your server can: internal APIs, admin panels, the cloud metadata service.

Example

Vulnerable
app.get("/preview", async (req, res) => {
  const response = await fetch(req.query.url);
  res.send(await response.text());
});
Fixed
const API = "https://api.example.com/quotes/";

app.get("/quote", async (req, res) => {
  const symbol = String(req.query.symbol);
  if (!/^[A-Z]{1,5}$/.test(symbol)) return res.sendStatus(400);
  const response = await fetch(API + encodeURIComponent(symbol), { redirect: "error" });
  res.json(await response.json());
});

How it happens

URL previews, webhooks, PDF renderers and import-from-URL features all fetch addresses supplied by users. A value of http://169.254.169.254/latest/meta-data/ asks a cloud VM for its own credentials; http://localhost:6379 talks to an internal Redis. Redirects and DNS names that resolve to private addresses get around naive blocklists.

How to fix it

Let input choose as little of the URL as possible.

  • Build the URL from a fixed base and validate only the variable part
  • If arbitrary hosts are a feature, resolve the name and refuse private, loopback and link-local addresses — on every redirect too
  • Connect to the address you checked, so DNS rebinding cannot swap it
  • Disable or re-check redirects, and cap response size and time

How RepoVerse finds it

  • HighRequest input chooses where the server sends an HTTP request

    Under the right network conditions the server can be made to call internal services or the cloud metadata endpoint (169.254.169.254) and hand the response back.

    data flow · CWE-918

  • HighRequest sent to a URL from the caller

    The address is chosen by whoever made the request, so this will happily fetch `169.254.169.254` and hand back the cloud credentials behind it.

    pattern · CWE-918

Questions

Is a URL starting with my own domain safe from SSRF?
When the host part is fixed and input only fills in the path, the server cannot be pointed elsewhere. RepoVerse treats a URL led by a literal host or a fixed base such as API_BASE_URL as safe and keeps reporting input that can still reach the host.
How does RepoVerse handle SSRF in its own website scanner?
Every request and every redirect is resolved and checked against private, loopback, link-local and reserved ranges, only ports 80 and 443 are used, and the socket connects to the address that was checked.

Related

Updated 2026-10-11