SSRF and IDOR
SSRF and IDOR are the modern high-impact pair — one makes your server fetch an attacker-chosen URL, the other returns a record by id with no owner check. Both are the same root error: a trust boundary the server never enforced. See how each works and the reflex that kills both.
On an authorized test, a tester finds a profile page with an “import avatar from URL” field. They paste a link to a cloud metadata address instead of an image. Seconds later the application’s HTTP client returns a blob of text — temporary cloud credentials, signed and ready to use. They never touched the network perimeter, bypassed no firewall, cracked no password. They simply asked the server to make a request on their behalf, and the server, sitting inside the trusted network with an IAM role attached, obliged. That is SSRF: the attacker doesn’t reach the internal service, the server reaches it for them. On the same engagement they open their own order at GET /api/orders/7741, change the number to 7742, and read a stranger’s shipping address and order total — no error, no alarm, because the query said WHERE id = ? and trusted a valid session to mean authorization. That is IDOR. Two bugs, two slots in the OWASP map, one identical root error: the server trusted a value it should have verified.
By the end of this lesson you’ll be able to explain how an attacker reasons about SSRF and IDOR, why both are really the same trust-boundary failure, and the server-side design reflex that closes both.
Authorization first: this is a defender’s autopsy
Everything below assumes a signed engagement with explicit scope — a deliberately vulnerable lab, a CTF, your own application, or a bug-bounty program with the target in scope. Pointing a server’s request feature at a cloud metadata endpoint you don’t own, or iterating object ids in an application you have no authorization for, is unauthorized access in most jurisdictions, full stop. Reading another user’s record is a privacy breach even if you “only looked.” We study the attacker’s reasoning for one reason: you cannot reliably close a vulnerability class you understand only as a checklist line. Read this as the autopsy a defender performs on their own code — learning to see the request and the query the way the attacker does, so the control is in the design before the bug ever ships. No copy-paste payloads here, only the mechanism.
SSRF: the server as a confused deputy
Server-side request forgery happens when an application takes a URL (or hostname, or IP) influenced by the user and makes an outbound request to it — a “fetch this image,” “import from this webhook,” “preview this link,” or “validate this RSS feed” feature. The attacker’s input never reaches the internal target directly; instead they trick the server into reaching it. This is the classic confused deputy: the server has privileges and network position the attacker lacks — it sits inside the VPC, can resolve internal DNS, holds an IAM role — and the attacker borrows all of it by choosing where the server points.
The mechanism has two ingredients. First, the request destination is attacker-influenced and the server doesn’t constrain it to a known-good set. Second, the network the server lives in contains something worth reaching that the attacker cannot reach from outside: a cloud instance metadata service, an internal admin panel on a private IP, a database that only accepts connections from the app tier, a Kubernetes API on a cluster IP. The textbook prize is the cloud metadata endpoint at the link-local address 169.254.169.254, which on a misconfigured instance hands out the machine’s IAM role credentials to anything that asks from on-box. SSRF reaches it because the request now originates on-box.
The attacker’s reasoning is a series of probes. Does the feature follow redirects (so an allowlisted host can 302 to an internal one)? Does it resolve DNS at fetch time (so a name that resolves to a public IP during validation can re-resolve to 127.0.0.1 at fetch — a DNS-rebinding race)? Does it accept non-http schemes like file://, gopher://, or dict:// that turn a fetch into a file read or a raw-socket write to Redis? Each answered “yes” is another way past a naive defense. This is why SSRF earned a spot on the OWASP Top 10 by practitioner vote despite thin historical data — cloud architectures made the blast radius enormous: one fetch feature can become full cloud-account compromise.
IDOR: a missing ownership check
Insecure Direct Object Reference is the production face of Broken Access Control. The server exposes a reference to an internal object — most often a database row id in a URL like /api/orders/7741 — uses that client-supplied value to look up the object, and never checks that the caller is allowed to see this particular object. The query is SELECT … WHERE id = ? when it needed to be SELECT … WHERE id = ? AND owner_id = ?. The attacker’s “exploit” is incrementing an integer. There is no payload, no special tooling: a valid session plus a guessable id is the whole attack.
The crucial reframing is that IDOR is authorization, not authentication. The attacker is fully logged in as themselves — that’s the point. Authentication asks “who are you?” and the session answers it correctly. Authorization asks “are you allowed to touch this object?” and the vulnerable code never asks it at all; it silently assumes a valid session implies permission for any id you can name. Object references leak constantly — in URLs, logs, referer headers, shared links, prior API responses — so “make the ids random UUIDs” only raises the guessing cost; it does not add the missing check. That is the senior trap to avoid: unguessable identifiers are obfuscation, not access control.
Same root, mirrored shape
Put SSRF and IDOR side by side and the symmetry is the lesson. In both, the server takes a value from the client and acts on it without enforcing the boundary it implies. IDOR trusts an object reference to mean “you may read this object.” SSRF trusts a URL to mean “you may fetch this resource.” Both substitute an unverified client-supplied value for a server-side authorization decision. The table makes the parallel explicit.
| Aspect | IDOR | SSRF |
|---|---|---|
| Trusted value | Object id in the request | URL / host the server fetches |
| Missing check | Does the caller own this object? | Is this destination allowed? |
| Attacker action | Increment / swap the id | Point the URL at an internal target |
| OWASP category | A01 Broken Access Control | A10 SSRF |
| Worst case | Bulk read/modify of others’ data | Metadata creds → cloud takeover |
| Root fix | Owner-scoped, deny-by-default authz | Egress allowlist + hardened metadata |
| False fix | Random UUID ids (obscurity) | Blocklist of “bad” IP strings |
▸Why this works
Why does a blocklist of bad addresses fail for SSRF where an allowlist of good hosts succeeds? Because the address space an attacker can express is effectively unbounded and ambiguous: 169.254.169.254 also speaks as the decimal 2852039166, as 0xA9FEA9FE, as octal, as IPv6-mapped forms, as a DNS name that resolves to it, or as an allowlisted host that 302-redirects to it. A blocklist must enumerate every spelling of “bad” and loses the moment it misses one; an allowlist enumerates the small, finite set of destinations you actually intend to reach and denies everything else by default. It’s the same deny-by-default principle as IDOR: start from “no” and grant the few known-good cases, rather than starting from “yes” and chasing every known-bad one.
How a defender closes both
For IDOR, the fix is structural, not cosmetic: enforce authorization server-side on the actual object and action, every time, computed from server-side session state — never inferred from a hidden UI element or a client-supplied claim. In a query that means scoping to the owner (WHERE id = ? AND owner_id = ?) or running an explicit policy check before returning the row. Centralize it so a new endpoint can’t forget it, and treat object-level authorization as the default every handler inherits.
For SSRF, defense is layered because no single control is sufficient. Constrain the destination to an allowlist of permitted hosts/schemes, resolve the hostname and validate the resolved IP against blocked ranges (link-local 169.254.0.0/16, loopback, RFC-1918 private space, cloud metadata IPs), and re-check after every redirect — validating the string alone loses to DNS rebinding and redirects. Then assume a control will eventually fail and harden the target: require a session-token metadata service (IMDSv2-style, which blocks the simple GET that classic SSRF relies on), give the instance the least IAM privilege it needs, and place egress firewall rules so the app tier simply cannot open arbitrary outbound connections. The recurring senior reflex across both bugs is the same: deny by default, verify the boundary server-side, and never let an unverified client value stand in for an authorization decision.
A feature lets users import a profile picture by pasting a URL; the server fetches it. On an authorized test, pointing it at the cloud metadata IP leaks credentials. Pick the fix that actually closes the SSRF.
A logged-in user changes `GET /api/orders/7741` to `7742` and reads someone else's order. What class is this, and what's the precise missing control?
Why is switching sequential order ids to random UUIDs an inadequate fix for IDOR?
Order the steps of the SSRF-to-cloud-takeover chain, from the attacker's input to the worst-case outcome:
- 1 Attacker supplies a URL to a server-side fetch feature
- 2 Server makes the request from inside the network with its IAM role
- 3 Request reaches the cloud metadata endpoint (169.254.169.254)
- 4 Temporary IAM credentials are returned to the attacker
- 5 Attacker uses the creds to pivot toward cloud-account compromise
- 01Explain the SSRF mechanism, why it's called a confused deputy, and why an egress allowlist beats a blocklist.
- 02How are SSRF and IDOR the same root error, and what's the distinct fix for each?
SSRF and IDOR are the modern high-impact pair, and seeing them as one error is the senior insight: in both, the server acts on a client-supplied value without enforcing the boundary that value implies. SSRF (OWASP A10) is the confused deputy — a ‘fetch this URL’ feature lets an attacker borrow the server’s network position and identity to reach an internal target, classically the cloud metadata endpoint at 169.254.169.254, turning one fetch feature into cloud-account compromise. IDOR (the production face of A01) is a missing ownership check — the server looks up a record by a client-supplied id and never asks whether the caller owns it, so incrementing an integer reads a stranger’s data; it’s authorization failing, not authentication. The fixes mirror the bugs: owner-scoped, deny-by-default authorization for IDOR, and a layered destination allowlist on the resolved IP plus a hardened metadata service for SSRF. Reject the obscurity traps — random UUIDs and string blocklists. Now when you see a handler that fetches by a client-supplied id, or a server that requests a client-supplied URL, your first question is the same: where is the server-side check that this value is actually allowed?
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.