BOLA Attack Detection: Spot Access Anomalies in API Logs
Last verified on Nov 8, 2026.
Broken object level authorization, or BOLA, is the flaw behind a lot of API data breaches. The API checks that you are logged in but not that the record you asked for is yours, so changing an ID in the URL returns someone else's data. OWASP ranks it first in the API Security Top 10 for 2023 and notes that attackers exploit it by manipulating the ID of an object sent in the request. IDs can be sequential integers, UUIDs or other strings.
The request that exploits it is a perfectly valid request. It returns 200, takes the usual time and comes from an authenticated user. The only way to see it is by looking at what one actor does across many requests.
What the evidence looks like
Enumeration has a recognizable shape in an access log: one actor, one route, many distinct object IDs, often in order.
2026-11-06T10:14:01Z actor=u_5521 GET /api/invoices/10001 200 4120
2026-11-06T10:14:01Z actor=u_5521 GET /api/invoices/10002 200 3987
2026-11-06T10:14:02Z actor=u_5521 GET /api/invoices/10003 403 112
2026-11-06T10:14:02Z actor=u_5521 GET /api/invoices/10004 200 4044This is an example. A normal user opens a handful of their own invoices. This actor reads thousands, walks the ID space and mixes 200s with 403s, which suggests authorization is partly working and partly not. Another variant is one actor touching many tenants. A third is one actor reading far more data than their own history, with no ordering at all because the IDs are random UUIDs lifted from another endpoint.
OWASP also lists resource consumption as its own risk, noting that response status, time and length analysis helps spot exploitation. Those three fields are enough to start.
How to detect it step by step
1. Log the actor and the object. Every API request should record an authenticated actor ID, the tenant, the route as a pattern such as /api/invoices/:id, the object ID and the owner of the object when you know it. Without the actor you can only group by IP, and shared IPs and proxies make that unreliable.
2. Count distinct objects per actor per window.
SELECT actor_id, route, count(DISTINCT object_id) AS objects, count(*) AS requests
FROM access_log
WHERE ts > now() - INTERVAL '15 minutes' AND status = 200
GROUP BY 1, 2
HAVING count(DISTINCT object_id) > 2003. Compare each actor to their own history. Fixed numbers fail because a support agent legitimately reads more than a customer. Compute each actor's daily distinct-object count over 30 days and flag the days above their own 99th percentile and above an absolute floor.
4. Find sequential walks. Order each actor's reads and look at the step between IDs:
WITH reads AS (
SELECT actor_id, ts, object_id::bigint AS id,
object_id::bigint - lag(object_id::bigint) OVER (PARTITION BY actor_id ORDER BY ts) AS step
FROM access_log
WHERE route = '/api/invoices/:id' AND ts > now() - INTERVAL '15 minutes')
SELECT actor_id, count(*) AS reads, count(*) FILTER (WHERE step = 1) AS in_order
FROM reads GROUP BY 1
HAVING count(*) > 100 AND count(*) FILTER (WHERE step = 1) > 0.8 * count(*)5. Track authorization failures. The OWASP Logging Cheat Sheet lists authorization failures among the events to log. A burst of 403 and 404 responses for one actor on object routes is the usual footprint of someone testing IDs.
6. Log ownership mismatches. If your handler loads a record and finds it belongs to a different tenant, log that before returning the error. This is the strongest single signal, and it costs one log line.
Thresholds and pitfalls
Start with "more than 100 distinct objects in 15 minutes and above the actor's own 99th percentile", then tune. Sequential detection works for integer IDs and fails for UUIDs, so use volume and failure ratio for those.
False positives to expect:
- Exports and sync clients. Mobile apps, integrations and bulk exports legitimately read many objects. Tag them by client ID and baseline them separately.
- Staff and support tools. They cross tenants on purpose. Log them as a separate actor class and review their volume on its own.
- Crawlers and search indexing. Known bots walk lists in order. Label them rather than excluding them.
- Shared accounts. A service account that fans out for many users looks like a mass reader. Split by the end user where possible.
Do not rely on rate limits to catch this. A slow walk, say one request every few seconds for days, stays under any limit and still reads everything.
What to do when it fires
Pull the actor's full request history for the day, and check which responses succeeded against objects they do not own. If the ownership mismatch log exists, it answers this directly. Suspend or rate-limit the actor, then fix the handler so the server checks ownership on every object access. Use unpredictable IDs as defense in depth only, since they do not replace the check.
Afterward, estimate exposure: the set of objects returned with 200 to that actor, and who owns them, is your notification list. Related guides: unusual data egress, bots and scraping, abuse and fraud and attack payloads. Sources of request logs include AWS API Gateway, Cloudflare and the AWS WAF integration.
Frequently Asked Questions
What is a BOLA attack?
Broken object level authorization is a flaw where an API accepts a request for an object ID without checking that the caller may access that object. Attackers change the ID to read or modify other users' data.
How do I detect BOLA in logs?
Log the actor, tenant and object ID on every request, then look for one actor reading many distinct objects, walking IDs in order or triggering bursts of authorization failures.
Does rate limiting stop BOLA?
No. A slow enumeration stays under any rate limit. The fix is an ownership check on every object access, and detection watches for the pattern across many requests.
What are false positives for access anomaly detection?
Bulk exports, sync clients, support staff and shared service accounts all read many objects. Baseline each actor class separately.
Sources
- API1:2023 Broken Object Level Authorization; attackers manipulate the ID of an object sent in the request; IDs can be sequential integers, UUIDs or strings (accessed Nov 8, 2026)
- Response status, time and length analysis helps identify resource consumption problems (accessed Nov 8, 2026)
- Authorization (access control) failures are among the events to log (accessed Nov 8, 2026)