API Security for Pentesters: BOLA, BFLA and Beyond
API pentesting for BOLA, BFLA and property level authorization, why two accounts are mandatory, and where object IDs hide outside the URL.
An API can require a valid token on every endpoint and still return another user’s data to any authenticated account.
That is why API pentesting needs more than scanning, fuzzing, and a list of status codes.
The real surface sits in objects, functions and properties. Authentication flows and business rules complete it.
The examples below assume a lab or an authorized penetration test.
Start by drawing the resources
Before sending payloads, I identify the resources.
Users.
Orders.
Invoices.
Files.
Projects.
Tokens.
Organizations.
Invitations.
Every noun that appears in documentation, the UI, or an API response may represent a useful object.
Then I map the actions.
Create.
Read.
Modify.
Delete.
Approve.
Export.
Invite.
Transfer ownership.
That object-and-action combination becomes the skeleton of the test.
Documentation helps, real traffic helps more
OpenAPI and Swagger can accelerate discovery.
Common paths include:
1
2
3
4
/openapi.json
/swagger.json
/api-docs
/docs
Do not treat the specification as a complete inventory.
Mobile and web clients may call endpoints missing from public documentation.
An interception proxy shows what the client actually uses.
JavaScript can reveal routes, versions, and parameter names too.
Authentication comes before authorization
First I understand how identity is established.
JWT?
Session cookie?
OAuth?
API key?
mTLS?
Then I learn how the API maps the request to a subject.
Only after that do I start swapping IDs.
This order matters because strange behavior may come from broken token validation, a session issue, or missing authorization.
BOLA remains one of the highest-value tests
Consider:
1
2
GET /api/v1/orders/4102
Authorization: Bearer TOKEN_USER_A
Account A receives its own order.
Change the ID:
1
2
GET /api/v1/orders/4103
Authorization: Bearer TOKEN_USER_A
If 4103 belongs to account B and the API returns it, the endpoint has Broken Object Level Authorization.
The backend trusts the object identifier without confirming ownership or permission.
A large UUID does not solve this.
It only makes object guessing harder.
If the identifier leaks through another response or log, that barrier disappears. The same applies to links and search endpoints.
BFLA targets the function
Now the question changes.
A normal account should not be able to call:
1
POST /api/v1/admin/users/42/disable
If the endpoint only checks for a valid session, a regular user can execute an administrative function.
That is Broken Function Level Authorization.
BOLA asks whether I can access somebody else’s object.
BFLA asks whether I can execute a function my role should not have.
Both can exist in the same API.
Property-level authorization catches modern APIs often
Consider a legitimate update:
1
2
3
{
"displayName": "Gabriel"
}
The backend deserializes the whole object and also accepts:
1
2
3
4
{
"displayName": "Gabriel",
"role": "admin"
}
This resembles what was traditionally called Mass Assignment.
The 2023 OWASP API Security Top 10 groups this broader class under Broken Object Property Level Authorization.
Testing needs to inspect both properties sent and properties returned.
An API may leak sensitive fields even when the user cannot modify them.
Use at least two accounts
Testing an API with one account limits the analysis.
I like to keep A and B.
Account A creates objects.
Account B creates equivalent objects.
Then I cross IDs, tenants, organizations, and actions.
If an administrative role exists, I add a third identity.
That small matrix quickly exposes horizontal and vertical authorization issues.
IDs live in more places than the URL
Do not change only /users/42.
Look for identifiers in bodies:
1
2
3
4
5
{
"ownerId": 42,
"projectId": 991,
"organizationId": 7
}
Also in headers:
1
X-Organization-ID: 7
And GraphQL variables:
1
2
3
{
"id": "UHJvamVjdDo5OTE="
}
The object may hide behind Base64, UUIDs, slugs, or opaque tokens.
The same question still applies: does the server revalidate the relationship between identity and resource?
HTTP methods can change behavior
An endpoint may block:
1
DELETE /api/v1/users/42
while forgetting to protect:
1
POST /api/v1/users/42/delete
Or GET may be correct while PATCH is not.
I test the resource, not only the first route I observed.
Old API versions matter too.
/v1/ may be protected while /v0/ remains reachable.
Rate limits need context
A 429 response proves some limit exists.
It does not prove the right operation is protected correctly.
Login, password reset and OTP deserve separate checks. Account creation, invitation sending and expensive operations do too.
I also identify whether the limit is tied to IP, user or token. Then I check endpoint-level and combined limits.
Headers such as X-Forwarded-For can influence weak implementations.
APIs often trust client metadata too much
Headers such as:
1
2
3
X-Role: admin
X-User-ID: 42
X-Original-URL: /admin
deserve attention when they appear.
Some are legitimately inserted by a reverse proxy.
The problem starts when the backend accepts the same header directly from the client.
That breaks the trust boundary between the edge and the application.
Scanners find symptoms. Flows reveal impact
Fuzzing helps discover endpoints.
Schemas help generate requests.
Scanners find repetitive configuration problems.
The highest-value part still requires understanding the business.
A “transfer project” endpoint may accept newOwnerId.
A “cancel payment” endpoint may accept a state transition that should be impossible.
A marketplace API may allow the same coupon to be applied through concurrent requests.
Those problems appear when you follow the workflow.
How I organize the test
Inventory first.
Then authentication.
Then object-level authorization.
Then function and property authorization.
Then business logic.
Then volume abuse, concurrency, and integrations.
That order gives coverage without turning the test into random payload collection.
Shadow APIs and old versions belong to the real scope
Documentation may show /api/v3.
An old mobile build may still call /api/v1.
A staging hostname may expose /internal/api.
A gateway may block one version while the origin still accepts another.
I search JavaScript, mobile applications, DNS, old documentation and error messages for those paths.
A deprecated API remains attack surface while it still responds.
Application signatures do not turn the client into a trusted source
Some APIs receive headers containing app version, device ID, build number or a signature derived from an embedded secret.
Those signals may help fraud detection.
The attacker controls their own device and can instrument the application.
If authorization depends only on a value the app can produce, I treat that value as client input.
The backend still needs to validate user identity and permission.
Response properties need authorization too
Pentests often focus on fields the user can send.
I also compare responses across roles.
An object may expose internalNotes, integration tokens, commercial margins or personal data that the UI simply ignores.
The client not displaying a field does not protect it when the API returns everything by default.
That closes the other half of Broken Object Property Level Authorization testing.
References
OWASP, API Security Project.
OWASP, API Security Top 10 2023.
OWASP, Web Security Testing Guide.


