Skip to contentExploitQuest

Lesson 3 of 3 in Broken Access Control

The Check On Every Object

The fix is the sentence that was missing — but where it goes, and why it cannot live in the menu or the id, is the part worth getting right. Enforce ownership on the object, on the server, every time.

3 min read

Not yet reviewed

Wrenlearner

So the fix is: check that the account belongs to me. That is one line. Why does anyone get it wrong?

Rookmentor

Because there are three tempting places to put the check that all feel like fixes and all fail. The only one that works is the least convenient.

Where the check has to live

The fix is to compare the object's owner to the person asking, and refuse if they differ. The mistake is putting that comparison anywhere except on the server, against the object, on every request.

Hiding the link (does nothing)

// only show the button for your own accounts
accounts.filter(a => a.owner === me)

Enforcing on the object (the fix)

const acct = account(params.id)
if (acct.owner !== session.user) return forbidden()

Hiding the link changes what the menu offers and nothing about what the server allows — the attacker types the URL the button would have pointed at. The check has to run where the object is fetched, not where it is displayed.

The non-fixes

Three things look like fixes for broken object-level authorisation and are not. Each one is worth being able to name, because each one ships to production believing it worked.

hide the button        remove the link from the page
use an unguessable id   swap 9000 for a random string
check at the door       verify access on the accounts page
  1. Line 1Removes it from the page and nothing from the server. The route is still there, and the attacker never needed the button — they typed the URL it would have pointed at.
  2. Line 2Raises the cost of *finding* an object, not the right to *read* it. And ids leak anyway — through referrers, logs, shared links, and every other object that mentions this one.
  3. Line 3Does nothing for the request that fetches one account directly. Authorisation belongs next to the fetch, not next to the front door.

Random ids are often recommended as a defence. If they are not an access control, are they worthless?

The rule

Every time the server fetches an object by an id that came from the request, it must confirm that the person asking is allowed that specific object — before returning it. Not in the menu, not by obscurity, not once at the entrance. On the object, on the server, every time.