Safeguard
Application Security•

API Pen Test Cases: BOLA and Function Level Authorization

The two API flaws testers find most in multi-tenant products, with curl checks you can run yourself on staging before the test starts.

Safeguard Security Team
5 min read

When OWASP published the Top 10:2025 on 11 November 2025, Broken Access Control stayed first. The Register's coverage notes the category covers errors such as URL tampering and missing API access controls, and quotes its prevention advice: deny by default, except for public resources. The OWASP API Security Top 10 (2023 edition) puts the same family up front: API1 is Broken Object Level Authorization and API5 is Broken Function Level Authorization.

These are the first things a competent tester checks in your API, and you can check them yourself in an afternoon.

What's affected

Any API where users belong to tenants or hold roles. Two distinct flaws:

  • BOLA (CWE-639, authorization bypass through a user controlled key): the API returns or changes an object because the caller supplied its ID, without checking that the caller may access that object. Tenant B reads tenant A's invoice by changing a number in the URL.
  • BFLA (CWE-862, missing authorization): a standard user calls a function meant for admins, such as creating users or exporting all records, because the route checks login but not role.

ASVS 5.0 states both as Level 1 requirements: V8.2.1 (function level access restricted to consumers with explicit permissions) and V8.2.2 (data specific access restricted to consumers with explicit permissions to specific data items).

How to check if you're exposed

Use two tenants on a staging copy, each with an admin and a standard user. Never run these against production data you cannot afford to touch.

BOLA check. Create an object as tenant A, then request it as tenant B:

# tenant A creates an invoice and notes the id
curl -s -X POST https://staging-api.example.com/v1/invoices \
  -H "Authorization: Bearer $TOKEN_A_USER" \
  -H "Content-Type: application/json" \
  -d '{"amount": 100}'

# tenant B asks for it (replace 8841 with the returned id)
curl -s -o /dev/null -w '%{http_code}\n' \
  https://staging-api.example.com/v1/invoices/8841 \
  -H "Authorization: Bearer $TOKEN_B_USER"

Anything other than 403 or 404 is a finding. Repeat for every object type, and for update and delete verbs. Also try IDs in request bodies, query strings and nested paths, not only the URL.

BFLA check. List every route from your API description and call the read only ones with a standard user token:

jq -r '.paths | to_entries[] | select(.value.get) | .key' openapi.json |
while read -r path; do
  code=$(curl -s -o /dev/null -w '%{http_code}' \
    "https://staging-api.example.com${path}" \
    -H "Authorization: Bearer $TOKEN_A_USER")
  echo "$code $path"
done | sort | grep -v '^40[134]'

The output is every route that answered a standard user with something other than 401, 403 or 404. Review each one: some are legitimately open, but admin routes should not appear. Test write verbs by hand, one at a time, in staging only.

What to do now

  1. Run both checks and open a ticket for every unexpected response.
  2. Enforce authorization in one place. A central policy check in middleware or the data layer is easier to test than checks scattered through handlers.
  3. Scope database queries by tenant. If every query includes the tenant from the session, an ID from the client cannot cross tenants.
  4. Use unguessable identifiers if you can, but treat them as defense in depth only. Hard to guess is not the same as authorized.
  5. Add an automated test per object type: tenant B requests tenant A's object and must get 403 or 404.
  6. Give the tester both tenants and both roles, so these cases are covered by the engagement.

How to avoid this trap

  • Do not rely on the frontend hiding admin buttons. The API is the boundary.
  • Do not check only that the caller is logged in. Check that this caller may perform this action on this object.
  • Do not test with one account. BOLA is invisible without a second tenant.

Why it matters

Automated scanners tend to miss these flaws because the response is a valid, well formed 200. The scanner has no way to know that the data belongs to someone else. A person with two accounts can check for them quickly, and enterprise customers care a great deal about tenant isolation.

Try it yourself

Pick your three most important object types and run the BOLA check on each in staging. If you have no second tenant to test with, create one first.

Click through it at your own pace, or open it full screen.

Quick check

What response should tenant B get when requesting tenant A's invoice?

403 or 404, never 200 with the data. Returning 404 also avoids confirming that the object exists.

Why do scanners often miss BOLA?

The response is valid and looks normal. Only knowledge of who owns the object reveals the flaw.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.