← marwandiallo.comlabs

BOLA / IDOR patterns

Eight patterns I look for in API code reviews and Burp traces. Each is paired with the actual fix — not "add authz", but the specific line of code or query change that closes the gap.

CRITICALBOLA01

Resource fetched by ID without owner scope

The endpoint queries for the resource by primary key and returns whatever it finds. Any authenticated user can change the ID in the URL and read other users' data. This is OWASP API #1 and accounts for the largest share of bug-bounty payouts.

fix: Scope the query by the authenticated principal: WHERE id = ? AND owner_id = ?. Do not 'fetch then check'.

HIGHBOLA02

403 Forbidden returned for cross-owner access

Returning 403 confirms the resource exists; an attacker can enumerate IDs to map other users' inventory. Real outcome: confirmed which order IDs are valid before they steal them.

fix: Return 404 Not Found for both 'does not exist' and 'not yours'. Indistinguishable.

HIGHBOLA03

Sequential / guessable resource IDs

Auto-incrementing IDs make BOLA discovery trivial — change ord_1001 to ord_1002 and try again. UUIDs don't fix the underlying authorization bug but raise the cost of mass enumeration significantly.

fix: Use UUIDv4 or another opaque identifier. Combine with the BOLA01 fix; UUIDs alone are not authorization.

HIGHBOLA04

Authorization in middleware only, not in the query

Middleware checks 'is the user logged in', then the route fetches by ID. This separation of concerns is correct for AuthN, dangerous for AuthZ — the route still needs to verify ownership.

fix: Push owner-scope into the data layer: a repository method that takes (id, principal) and refuses to return cross-tenant rows.

HIGHBOLA05

Mass-assignment on update endpoint

An update endpoint accepts the full order object including ownerId, allowing an attacker to reassign their own order to another user, or to escalate via role= fields.

fix: Whitelist updatable fields. Never trust client-supplied id/owner/role/createdBy on writes.

MEDIUMBOLA06

Admin override flag in client request

Endpoint accepts ?admin=true or X-Admin: 1 and skips the owner check. Frequently used by internal tools and never removed before launch.

fix: Trust only server-side claims (session, JWT validated server-side). Strip request-level admin flags at the edge.

MEDIUMBOLA07

GraphQL by-ID query without owner scope

GraphQL resolvers commonly expose order(id: ID!) that resolves any record. Same bug as REST; harder to spot because of the schema layer.

fix: Pass the principal into the resolver context. Apply ownership in the data loader, not the resolver. Consider Relay-style global IDs that encode owner.

LOWBOLA08

Verbose error messages leak resource shape

Errors like 'order not found in tenant t_acme' confirm tenant existence and naming. Useful for an attacker building a cross-tenant attack chain.

fix: Return generic errors to clients. Log the detail server-side with a correlation ID.

The two questions to ask any list-or-fetch endpoint

  1. Whose data does this query return? If the answer isn't "the authenticated principal's", the next question matters.
  2. Where is ownership enforced? "In the route handler" is a yellow flag. "In the data layer, in the same query that does the fetch" is what you want.