Horizontal Privilege Escalation
Walk an IDOR from both sides: a logged-in customer changes one digit in a request and reads other customers' accounts, then a security engineer ships the missing ownership check.
What Is Horizontal Privilege Escalation?
Horizontal Privilege Escalation, Insecure Direct Object Reference in OWASP terms, is one of the most common bug classes in modern web APIs because it lives in the gap between two checks that feel like the same check. The session middleware ran. The user is authenticated. Surely that means they are allowed to read this resource? It does not. Authentication tells you who is calling; authorization tells you whether that caller is allowed to reach this particular resource. Most IDOR bugs are not missing security code so much as one missing line of comparison: does the resource's owner match the session's user? In this exercise you start as Bob, an attacker who signed up for Sablefin, a consumer money app, with a throwaway identity. He opens his own monthly statement and, using the browser network tools, watches the request the app makes to load it: a plain GET to the statements API carrying his valid session token, with his statement id a single integer on the end of the path. He edits that captured request directly in the browser's network tools and changes one digit, then resends it. The response comes back as another customer's statement, name, masked account number, balance, and transactions, with his session still on the request. One adjacent id is all it takes to prove the endpoint trusts the id in the URL instead of checking ownership. As Alice, an application security engineer at Sablefin, you walk into a SEV-1: one session pulled thousands of statements across thousands of accounts overnight. You open the handler, find that it confirms the caller is logged in but never confirms the statement belongs to them, and ship the fix, an ownership comparison between the statement's owner and the session's user that refuses the request with 403 when they do not match. You reproduce the same tamper from your own session against the patched endpoint and watch it return FORBIDDEN. The exercise closes on the distinction between authentication and authorization, why UUIDs and rate limits are defense in depth rather than the fix, and why ownership must be decided from the session, not the request.
What You'll Learn in Horizontal Privilege Escalation
- Distinguish authentication (who is calling) from authorization (whether this caller may reach this resource), and identify which check IDOR bypasses
- Recognize the IDOR signature: one valid session, many different resource ids, successful responses across many owners
- Apply the ownership comparison as the primary defense: compare the resource's owner to the session's user and refuse with 403 when they do not match
- Explain why random UUID identifiers, rate limiting, and stronger passwords each fail as a primary defense against IDOR, the bug is missing authorization, not guessable ids
- Decide ownership from the server-side session rather than from any value the caller controls in the URL, body, or headers
Horizontal Privilege Escalation — Training Steps
-
A normal customer view
Sablefin is a money app that anyone can sign up for. Bob did, with a throwaway identity, and now he is just a customer looking at his own account like everyone else. He opens his monthly statement. Nothing here is off-limits to him. It is his account, his data, his login.
-
Watch the request
Before Bob changes anything, he wants to see the request his own app makes to load this page. He opens the browser network tools and reloads the page, so the call the app sends to fetch his statement is captured.
-
The endpoint and the id
The network tools captured the call. It is a plain GET to the statements API, and the whole address of Bob's statement is a single integer on the end of the path.
-
The token that proves who he is
The same captured request carries Bob's session token. The server reads it to confirm the caller is a signed-in customer.
-
Change one digit
Bob never touches the app's interface again. He edits the captured request directly, dropping the id by one and resending it with his own token still attached. If the server hands back a statement that is not his, it never checked ownership: it trusted the id in the URL to decide what to return.
-
Somebody else's account
Different name, different account number, different balance, and Bob's token still on the request. The server had no reason to serve this, and it served it anyway. One adjacent id is all it takes to confirm the bug. Everything past this point is a ten-line script walking the id space overnight while Bob sleeps.
-
Knowledge check
You just watched a logged-in customer read another customer's statement by changing one number. Lock in why.
-
The alert lands
You own Sablefin's customer accounts service. Overnight, anomaly detection flagged one session pulling statement after statement across accounts that were not its own. Security operations has emailed you the details.
-
Open the handler
Open the statements handler and look at how it decides what to return.
-
Spot the flaw
The handler fetches the statement by the id in the URL and returns it. Between those two lines, the check that should be there is not.