Requests, files, and servers
Control server-side requests
Prevent SSRF by restricting destinations, protocols, redirects, DNS behavior, credentials, and response sizes for server-side fetch features.
A server-side fetch runs from inside your network. It can reach internal services and cloud credentials the user’s browser never could. SSRF turns a URL input into a path into that private space.
The naive importer trusts a user-supplied URL directly.
// Vulnerable: fetches wherever the user points
const image = await fetch(req.body.url)
An image importer accepts a public URL that redirects to http://169.254.169.254. The first host passes validation, but the server follows the redirect into cloud metadata.
Validate the destination you actually connect to
Prefer fixed provider endpoints. When you must accept a URL, allowlist the scheme, resolve the hostname, and reject private and metadata ranges. Because DNS can resolve differently on a second lookup, validate the resolved address.
import dns from 'node:dns/promises'
import ipaddr from 'ipaddr.js'
async function assertPublic(hostname) {
const { address } = await dns.lookup(hostname)
const range = ipaddr.parse(address).range()
if (['private', 'loopback', 'linkLocal', 'uniqueLocal'].includes(range)) {
throw new Error('blocked destination') // 169.254.x, 10.x, 127.x, etc.
}
return address
}
Then cap what the request can do: disable redirect following, bound the time, and limit the bytes read. Use a client with no ambient credentials attached.
const res = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(3000) })
A DNS name can also resolve differently after an initial check. Validate the resolved destination used for each connection and cap redirects, time, and response bytes.
Test the fetcher with a public image, loopback, a private address, a metadata address, and a redirect to each blocked range. Capture the destination decision and prove the client sends no ambient credential.
Lesson completed