1

Detection

Role Mapping

🔎

Detection

Role Mapping

Before testing authorization, first build a matrix that maps every role against every sensitive action the application exposes. This defines the expected authorization behavior and gives the tester something concrete to test against.

Action              | user | editor | admin
--------------------|------|--------|------
view own profile    | yes  | yes    | yes
edit own post       | no   | yes    | yes
edit any post       | no   | no     | yes
delete user account | no   | no     | yes

The matrix should also capture ownership or contextual conditions where permissions aren't determined by role alone:

Action              | Condition
--------------------|-----------------------------
edit post           | user owns the post
edit post           | admin can edit any post
view invoice        | user belongs to its tenant
delete user         | requester has admin permission

This matters because:

role = editor
        ↓
Does NOT automatically mean
        ↓
can edit every post

The actual rule might be:

editor + owns post → allow
editor + does not own post → deny
admin + any post → allow

Once the expected rules are written down, testing becomes systematic:

Role / Context
      ↓
Action
      ↓
Expected: allow or deny?
      ↓
Send request
      ↓
Actual: allow or deny?
      ↓
Compare

Without this mapping, authorization testing becomes guesswork. You might verify that ordinary users cannot access the admin panel while completely missing that an editor can modify another user's content.

2

Detection

Endpoint Testing

🔎

Detection

Enumerate every endpoint and method the application exposes — not just the routes visible through the UI — and test each one using the sessions and roles defined in the authorization matrix.

Sources for endpoint discovery:

→ Crawl the application through Burp Suite / OWASP ZAP
   while exercising every feature

→ Inspect JavaScript bundles
   → API routes may exist without corresponding UI elements

→ Review exposed API specifications
   → Swagger / OpenAPI

→ Review GraphQL endpoints
   → introspection or observed queries/mutations

→ Look for alternate API versions and legacy routes
   → /api/v1/...
   → /api/v2/...

Then test the same endpoint with different roles:

Endpoint: DELETE /api/users/4522

admin session
→ expected: allow

editor session
→ expected: deny

standard user session
→ expected: deny

Don't stop at the obvious role. An endpoint intended for administrators should still be tested with a normal user's session because the entire point of authorization testing is discovering whether the server actually enforces the intended restriction.

3

Detection

HTTP Method Testing

🔎

Detection

The same endpoint can expose different operations through different HTTP methods, and authorization checks are sometimes implemented for one method but accidentally omitted from another.

GET    /api/orders/4521
→ ownership check ✓
→ allowed for owner

DELETE /api/orders/4521
→ ownership check missing ✗
→ unauthorized deletion possible

The URL is identical, but the security-sensitive operation is different. Therefore, each method needs to reach the appropriate authorization check.

Also test HTTP method override mechanisms where the application or framework supports them:

POST /api/orders/4521
X-HTTP-Method-Override: DELETE

If the server interprets that request as DELETE, but authorization middleware only recognizes the literal DELETE method, the effective operation could bypass the intended check.

The testing pattern is:

Same resource
   ↓
GET / POST / PUT / PATCH / DELETE
   ↓
Does each effective operation enforce authorization?

This is particularly important for state-changing methods such as:

POST   → create / trigger action
PUT    → replace
PATCH  → modify
DELETE → remove

Authorization should follow the effective action being performed, not merely the HTTP method or URL that happens to reach it.

4

Detection

Parameter Manipulation

🔎

Detection

Parameter Manipulation

Parameter manipulation is the systematic testing of client-controlled authorization-relevant values — such as object IDs, account IDs, tenant IDs, and role fields — by changing them and observing whether the server still enforces the correct authorization decision.

Test every location where these values can appear:

URL path
→ /api/invoices/9932

Query parameter
→ /api/invoices?id=9932

JSON body
→ {"invoice_id": 9932}

Form field
→ invoice_id=9932

Header
→ X-Account-ID: 772

Cookie
→ account_id=772

For example:

Baseline:
GET /api/invoices/9931
→ own invoice
→ 200 + invoice data ✓

Test:
GET /api/invoices/9932
→ another user's invoice
→ should be denied ✗

The important question isn't simply whether the parameter can be modified — all client-controlled input can be modified. The question is whether the server independently verifies that the resulting resource or action is authorized for the requester.

A particularly important case is duplicate or conflicting parameters:

POST /api/invoices/update/9931

{
    "invoice_id": 9932,
    "status": "approved"
}

If the application authorizes 9931 but performs the operation against 9932, the authorization check is protecting the wrong object.

Likewise:

Path:
 /accounts/771/reports

Body:
 {"account_id": 772}

If authorization is performed against account 771 but the application ultimately retrieves data belonging to 772, the check can be bypassed through the second parameter.

The testing pattern is therefore:

Find every authorization-relevant parameter
        ↓
Change it individually
        ↓
Test conflicting/duplicate values where applicable
        ↓
Observe which value controls the operation
        ↓
Verify authorization is applied to that exact resource
5

Detection

Response Comparison

🔎

Detection

Authorization testing shouldn't stop at checking whether the server returns 200 or 403. A request can be correctly denied while still revealing sensitive information through subtle differences in the response.

For example:

Own resource:
→ 404
→ body: "Not found"

Existing resource belonging to someone else:
→ 404
→ body: "Not found"

Non-existent resource:
→ 404
→ body: "Invalid ID format"

At first glance, all three appear to be blocked. But the difference between the responses can reveal information about the underlying resource.

Useful things to compare include:

HTTP status code
Response body
Response length
JSON fields present/absent
Error messages
Response headers
Redirect behavior
Response timing

For example:

Request A → 404 + 42 KB response
Request B → 404 + 2 KB response

Even though both requests return 404, the difference may indicate that the server followed a different internal path depending on whether the resource existed.

Timing can provide another signal:

Existing resource
→ database lookup + authorization check
→ slower response

Non-existent resource
→ lookup fails immediately
→ faster response

Repeated measurements are important because individual requests naturally vary. The goal is to determine whether a consistent statistical difference exists, not whether one request happened to be 20 ms slower.

This matters because an application can have:

Object access
→ correctly denied ✓

Resource existence
→ still revealed ✗

An attacker may then enumerate valid:

user IDs
order IDs
invoice IDs
account IDs
file IDs

and use that information to make subsequent attacks much more targeted.

The testing workflow is therefore:

Send equivalent requests
        ↓
Compare complete responses
        ↓
Identify consistent differences
        ↓
Determine what information the difference reveals
        ↓
Check whether that information should be exposed
6

Detection

Multi-User Testing

🔎

Detection

Authorization testing cannot be performed properly with a single account. Horizontal authorization requires multiple accounts at the same privilege level, while vertical authorization requires accounts representing different privilege levels.

A practical minimum test set might be:

Account set:

user_A  (standard)
→ access user_B's resources → should be denied

user_B  (standard)
→ access user_A's resources → should be denied

admin_A (admin)
→ access administrative functions → should be allowed
→ access permitted user resources → should be allowed

For stronger coverage, also test the reverse direction where the authorization matrix says it should be denied:

user_A
→ admin-only function → deny

admin_A
→ user_A's resource → allow, if admin policy permits

The important point is that authorization is not simply:

"Does this account work?"

It is:

Who is requesting?
        +
What are they trying to access or do?
        +
Who owns the resource?
        +
What privilege does the requester have?
        ↓
Should this specific request be allowed?

For horizontal testing, switch the identities while keeping the requested resource constant:

user_A → resource_A → expected allow
user_B → resource_A → expected deny

user_B → resource_B → expected allow
user_A → resource_B → expected deny

For vertical testing, keep the operation constant while changing the privilege level:

user_A  → admin action → expected deny
admin_A → admin action → expected allow

This also helps catch asymmetric authorization bugs where one direction is protected but another isn't.