The shop can look up a parcel by fetching the courier’s tracking page for you. You hand it a URL, the server fetches it, and shows you what came back. The catch: it will fetch any URL, from where the server sits, including addresses you could never reach yourself. This is server-side request forgery, and it turns the server into your proxy onto its own private network.
New to this? What is SSRF?
Server-side request forgery (SSRF) is when you can make a server send HTTP requests to a target you choose. Because the request comes from the server, it reaches things you cannot: internal admin panels, databases, and cloud metadata services on private addresses.
The fix is to decide up front what the server is allowed to fetch, and refuse everything else, especially internal, loopback and link-local addresses.
Make sure the shop is running at http://localhost:3000.
No account needed.
1 · Find it
The tracker takes the courier URL as a parameter in the address bar. Type this into your browser’s address bar and press Enter:
http://localhost:3000/track?url=https://example.com Whatever you put after url=, the server goes and fetches, then hands you the body.
That is the whole vulnerability: it is fetching on your behalf, with the server’s reach.
2 · Exploit it
Change the url= value to point at something internal. The shop’s own admin
export, for one:
http://localhost:3000/track?url=http://127.0.0.1:3000/admin/export Back comes the full customer list, fetched by the server. On a cloud host, the classic target is the metadata service, which often hands out credentials to anyone who asks from inside:
http://localhost:3000/track?url=http://169.254.169.254/latest/meta-data/ Why it matters: SSRF is a favourite first step into a network. Reach an internal service, a metadata endpoint, or a database admin port, and a read-only fetch becomes a foothold.
3 · Understand it
The handler fetches the user’s URL with no questions asked:
app.get('/track', async (req, res) => {
const upstream = await fetch(req.query.url); // any URL, from the server
res.send(await upstream.text());
}); 4 · Fix it
Do not fetch arbitrary URLs. Allow only the couriers you actually use, and refuse internal and private addresses:
const r = await fetch(req.query.url); const u = new URL(req.query.url);
const allowed = ['track.royalmail.com', 'dpd.co.uk'];
if (!allowed.includes(u.hostname)) {
return res.sendStatus(400);
}
const r = await fetch(u); Better still, do not proxy at all: build the tracking link and let the customer’s own browser open it. If you must fetch, resolve the host and block private, loopback and link-local ranges too, or a redirect will walk you back inside.
The general rule: if the server fetches a URL, the user must not choose it freely. Allowlist the destinations and treat every hop, including redirects, as untrusted.