Card 03/ 10
Authorisation Example: Changing /orders/1001 to /orders/1002
A customer edits one number in a URL and reads a stranger's order, address and phone number.
Broken object level authorisation is easiest to see on one request, with and without the ownership check. A bug bounty report arrives with two order numbers in it: the researcher owns order 1001, changed it to 1002 in the URL, and received another customer's order. The responses are illustrative.
Without the check200, another customer's data
GET /orders/1002 with the researcher's valid token returns 200 OK with the order, the other customer's name, delivery address and phone number. Repeating it with 1003, 1004 and onwards returns every order in the system.
With the check404, nothing revealed
The handler loads the order where the id is 1002 and the customer is the caller. No row matches, so it returns 404 Not Found, exactly as for an order that does not exist. The researcher learns nothing.
Where the check lives. The fix is one condition in the query, WHERE id = :id AND customer_id = :caller, or a policy every order handler calls. A check added only to GET leaves PATCH /orders/1002 and DELETE /orders/1002 open, so the test covers every method.
Sequential ids made it easy, but are not the cause. Numbers that count up let an attacker walk through every order in a loop. Random UUIDs make guessing hard, but ids leak in shared links, emails and logs, and a leaked id would still open someone else's order. Random ids are a sensible extra; the ownership check is the fix.
Why it is easy to miss. The endpoint is authenticated, the code reads naturally, and every test run by one user passes. Only a test with two users finds it.
← → move through sections and cards