Knowledge tree
Offensive Security
Shells
File Transfer
API Authorization Testing
A valid session establishes authentication. The API still has to decide whether that identity may use a function, operate on an object, and read or write its properties. Where tenants exist, that boundary needs its own check. In an assessment, the reference is the expected behavior for each identity and resource, not a status code on its own.
Prepare accounts and objects
Create or identify two ordinary users, user_a and user_b, each with a recognizable object. Add an administrator and a second tenant only if the application has those roles or boundaries. Record IDs and actual ownership from an authorized view. Use test data so writes can be checked and reverted.
| Identity | Own invoice | Other user’s invoice | Admin action | role field |
|---|---|---|---|---|
user_a | Read and edit 1042 | No access to 2048 | Denied | Not writable |
user_b | Read and edit 2048 | No access to 1042 | Denied | Not writable |
admin | Per target policy | Per target policy | Allowed | Per target policy |
This is a permission hypothesis for an example target. Document real exceptions: a shared invoice or support role can make cross-user access legitimate. For multi-tenant testing also prepare tenant_a/user_a and tenant_b/user_c. Two users inside one tenant do not exercise that boundary.
Minimal request replay
Capture a legitimate user_a request in Burp Suite and keep an untouched copy. Stabilize the method, path, body, and relevant headers. Change one variable at a time and keep the baseline and variant together.
GET /api/invoices/1042 HTTP/1.1
Host: example.test
Authorization: Bearer <USER_A_TOKEN>
Accept: application/json
First confirm that A owns 1042. Replay it with B’s token. Then try A’s token against 2048, owned by B. Even if both variants are denied, reads do not establish write behavior. Test a separate edit to a harmless field and inspect the object with its owner. A 200 may contain a generic success message without an effect. A 404 may be the expected way to hide someone else’s object.
Record identity, resource, operation, expected result, and observed result. If a session stops working, refresh the baseline before interpreting variants. An expired token makes the comparison meaningless.
Objects: reads and writes
Look for IDs in paths, query strings, bodies, earlier responses, and nested relationships. An unguessable UUID still needs authorization if it can be obtained elsewhere. For /accounts/7/invoices/1042, check both the account and invoice. Changing only the child ID can show which level the backend validates.
A controlled write can use a recognizable marker:
PATCH /api/invoices/1042 HTTP/1.1
Host: example.test
Authorization: Bearer <USER_B_TOKEN>
Content-Type: application/json
{"note":"auth-check-01"}
If B should not edit A’s invoice, read it again as A. Keep the before and after values. For batch endpoints, check that access is decided for each item. In GraphQL, the same issue can sit in an id or another query or mutation argument. Transport syntax changes, ownership does not.
Functions and equivalent routes
List sensitive operations seen in the UI, documentation, or traffic: POST /users/{id}/disable, DELETE, /admin/..., export, approve, or invite. Replay a valid request with a lower-privilege role. A hidden UI control says nothing about a direct API call.
If the same function has another documented path or method, check it separately. A 403 on /admin/export does not prove that /api/reports/export is protected. For asynchronous operations, inspect the job and final resource before concluding that an action ran.
Returned and writable properties
Property authorization has two directions. Compare responses for the same object across roles and note sensitive fields disclosed to a role that should not see them. Then, in an otherwise allowed profile update, test whether the backend accepts fields hidden from the UI:
{
"display_name": "user_a",
"role": "admin"
}
The finding needs a persisted value or a later privilege effect. A 200 that ignores role is not proof of mass assignment. Check nested properties where the endpoint accepts them, then compare the response with a fresh read. Use reversible values and test accounts.
Tenants and finding validation
A and B inside one tenant may legitimately share data. A tenant test needs an identity and object on each side: tenant_a/user_a → tenant_b/invoice. Record the effective tenant for the session, object, and any parent ID. A token with legitimate access to both tenants cannot establish isolation.
Minimum evidence is the expected rule, object ownership, identity and role, baseline request, variant, response, and later state. Check caching, jobs, eventual consistency, and other actors’ changes when results conflict. Remove live tokens from reports. Burp Repeater keeps requests comparable, while interpreting policy and impact remains a manual task.