Tirup Mehta
Tirup Mehta
WritingYour Server Is Too Polite: How Attackers Trick It Into Hacking Itself

Your Server Is Too Polite: How Attackers Trick It Into Hacking Itself

Security/12.Aug.2026/4 min read
#security#ssrf#backend
← All Articles
TL;DR

Your server fetches whatever address a stranger hands it, including secret internal ones in disguise (2130706433 is just 127.0.0.1). This is a 40-year-old flaw pattern called the confused deputy, and it keeps working because helpfulness is the vulnerability. How the trick works, why cloud design makes it worse, and the one atomic fix.

Picture this. Your app lets users paste a profile-picture URL, and your server politely downloads it. Nice feature. Now an attacker pastes a URL that secretly points inside your own network: at your cloud provider’s master-keys page, reachable only by your server. Your obedient server fetches it and hands the secrets back. Robbed by your own helpfulness.

This is SSRF, short for Server-Side Request Forgery. But the name undersells it, because the pattern is far older than the web. In 1988, researchers named it the confused deputy problem: a program with authority (your server, holding cloud keys) gets tricked by a less-privileged party (a stranger with a URL box) into misusing that authority. Every SSRF is a confused deputy. So is every open redirect, every clickjacking attack, every prompt-injection jailbreak. Learn to see the deputy and you start seeing the same shape everywhere, including in AI agents, which are deputies with far more authority than any web server ever had. (My swarm post is arguably one long confused-deputy story.)

Why a simple check isn't enough

Your first instinct is a blocklist: reject internal addresses. It fails because computers understand the same address many ways, and your check recognizes one. Type http://2130706433: nonsense to your eyes, plainly 127.0.0.1 (your own machine) as a single decimal number. 0177.0.0.1 (octal) and 0x7f000001 (hex) both mean "home." Your server's fetching code understands all of them. Your regex almost certainly does not.

Then the sneakier trick: DNS rebinding. Your server checks an address, sees a safe public IP, approves it. A split second later, at connect time, the attacker's DNS answers with an internal address instead. Check and connection are two separate moments, and the attacker lives between them. Any "verify first, connect later" defense has this hole poured into its foundation.

Why the cloud made it worse

Here's the second-order bit that took me the longest to really get: SSRF was a moderate bug until cloud providers put magic URLs inside every server. The metadata endpoint (169.254.169.254) hands out credentials to anything that asks. No authentication, by design, because "only the machine itself can reach it." SSRF breaks exactly that assumption: suddenly the whole internet can ask, through your server as a mouthpiece. Cloud convenience created the treasure chest; SSRF just opens it. The providers have been slowly adding friction (token-required metadata versions), but the chest is still there on countless default configurations.

Going deeper: the fix that closes both holes

Engineers, your section. Everyone else already has the full idea.

The fix must be atomic: inspect the address on the very socket carrying the request, not in a separate pre-check. Resolve every DNS record, reject private/loopback/link-local/metadata ranges, re-validate every redirect hop:

security/safe-fetch.ts The Fix
import ipaddr from 'ipaddr.js';
import dns from 'dns/promises';

const BLOCKED = ['unspecified', 'broadcast', 'linkLocal', 'loopback', 'private', 'reserved'];

export async function safeResolveHost(hostname: string): Promise<string> {
  // 'all: true': one malicious record can't hide behind a clean first entry.
  const addresses = await dns.lookup(hostname, { all: true });
  for (const r of addresses) {
    const addr = ipaddr.parse(r.address);
    if (BLOCKED.includes(addr.range())) throw new Error('restricted range: ' + r.address);
    if (addr.toNormalizedString() === '169.254.169.254') throw new Error('cloud metadata blocked');
  }
  return addresses[0].address;
}
// Rules around it: max 3 redirects, re-validate every hop,
// never forward Authorization headers cross-origin.

Try it tonight: feed 2130706433, 0177.0.0.1, and 0x7f000001 to your own URL validator. If any pass, you have homework. And next time you design any feature where user input steers a privileged action: file reads, redirects, database queries, agent tool calls. Ask the deputy question first: who actually wields the authority here, and who steers it?

The Takeaway
  • SSRF is one face of the confused deputy. Learn the shape, spot it in redirects, frames, and AI agents too.
  • Regex can't see disguised addresses. Validate resolved IPs, on the connection itself, on every hop.
  • Cloud metadata endpoints are the treasure; your fetcher is the map. Guard both.