Research

A valid request the app should have refused.

Broken access control is the most common serious web risk — and the one a scanner structurally cannot see, because nothing about the request is malformed. This is why it is invisible to pattern-matching, and how we turn it into proof by replaying the same request as every identity and comparing the answers to the rules the app is meant to enforce.

Research · 24 October 2026By Enes Çetinkaya

The short version: broken access control is not a malformed request a scanner can flag — it is a perfectly ordinary request that the application should have refused for whoever sent it. There is no error to match. The only way to prove it is to send the same request as different identities and compare: the owner is refused, a stranger is served the owner's data. That difference, reproduced from a clean session, is the finding.

Why a scanner can't see it

A scanner inspects a request and its response in isolation and asks generic questions: does the response contain a SQL error, a stack trace, a reflected marker, a known-vulnerable version string? Broken access control answers "no" to every one of them. The request is well-formed. The response is a clean 200 with a normal-looking body. Nothing is reflected, nothing errors. By every signal a scanner has, the page is healthy.

But the body belongs to another customer. The failure is not in the request or the response — it is in the gap between what the application returned and what its own authorization rules say it should have returned for this identity. A tool that looks at one request cannot see that gap, because the gap is defined by a second request: the one made as a different user. This is why access control sits at the top of the OWASP Top 10 and stays there, and why automated scanning has never meaningfully dented it.

Same request GET /invoice/1042 200 · owner sees A’s data authorized (control) 403 · refused control holds 200 · A’s data returned should be 403 — the break as owner (A) as anon client replayed as user B
The identical request — GET /invoice/1042 — is replayed under each identity. The owner is served and the anonymous client refused, exactly as the rule requires; replaying it as a different tenant returns the owner’s data where a 403 was due. That cross-identity hit is the BOLA finding.

IDOR and BOLA are the same bug, twice

You will see two names for this. IDOR — Insecure Direct Object Reference — is the classic web framing: a user changes an identifier in a URL or form (/invoice/1042 → /invoice/1043) and reaches an object that is not theirs. BOLA — Broken Object Level Authorization — is the API framing of the identical failure, and it is number one on the OWASP API Security Top 10. APIs made it worse: object identifiers are everywhere, endpoints are numerous and often under-documented, and the client is frequently the only thing enforcing "you can only see your own."

The related siblings follow the same logic: object-property-level authorization (you can see the record but not a field you shouldn't), and function-level authorization (a viewer reaching an admin-only action). All of them share the property that defeats scanners: the request is legitimate; only the identity behind it is wrong.

The replay-and-compare method

Because the bug is defined by identity, the test has to be defined by identity too. You bring a test account per role — an owner, a second tenant, an unauthenticated client. The engine learns the application's objects and the rule that is supposed to govern each one ("an invoice is visible only to its owning workspace"), then forms a specific, testable hypothesis and runs the same request as each identity:

  • As the owner — establishes the real, authorized response (the control).
  • As a different tenant — the one that should be refused.
  • As an unauthenticated client — the floor; nobody anonymous should see it.

Then it compares the answers to the rule, not to a generic pattern. This is the part a scanner has no concept of: the oracle is the application's own business rule, supplied by you as scope and context, not a signature baked into a tool.

Control 403, test 200 — the two-sided proof

A finding here needs both halves, or it is noise:

  • The control is correctly refused. The owner-only path returns 403 (or an empty result) to the stranger and to the anonymous client. This proves the application has a rule and normally enforces it — so a leak is a real break, not a resource that was public all along.
  • The test is wrongly allowed. A different identity's request returns 200 carrying the owner's data. This is the breach.

One without the other proves nothing: a lone 200 might be a public endpoint; a lone 403 might just be a healthy control. The pair — refused for the control, served for the test, against the same object — is what makes it a confirmed authorization failure. We then reproduce that pair from a clean session and capture the requests, the responses and the replays as hash-chained evidence, so the finding carries its own proof. A full worked example of exactly this — cross-workspace invoice access — is on the sample report.

Honest limits

Proving one object is reachable across tenants does not prove every object is — coverage is evidence, and we report which objects and roles were actually exercised versus which had no eligible surface or were blocked before we could reach them. And as always, a clean result is not a certificate: it means the paths we tested held, not that the application is free of access-control bugs everywhere. The value is in what was proven, stated exactly, and in the replays that let your engineers reproduce it and confirm the fix.

FAQ

Why can't a scanner find broken access control?

A scanner tests one request in isolation against generic patterns. Broken access control is a valid request the app should have refused for this identity — there is no error signature to match. The bug is the gap between what the app returned and what its own rules require, and that gap only appears when you replay the request as a different identity and compare.

What is the difference between IDOR and BOLA?

The same class from two angles. IDOR is the web-app framing (reaching an object that is not yours by changing an identifier); BOLA is the API framing of the same failure and is number one on the OWASP API Security Top 10.

How do you prove it is real and not a false positive?

Two signals together: the control (owner or anonymous) is correctly refused — 403 or empty — while the test (a different identity) is wrongly allowed — 200 with the other tenant's data. Then the pair is reproduced from a clean session. A difference that reproduces is a finding; a one-off is held back.

This is one class of many. SuperPentest tests access control by replaying as every identity you supply, and reproduces each candidate before calling it a finding. Read the worked BOLA report, the prompt-injection teardown, or book a demo.