parlov docs
OraclesExistenceStatus code diffAuth & Privilege Elicitation

Auth & Privilege Elicitation

The order in which a server evaluates authentication, authorization, and resource existence determines whether auth-related status codes leak information.

Implemented

The order in which a server evaluates authentication, authorization, and resource existence determines whether auth-related status codes leak information. When a server resolves the resource before checking credentials or permissions, the differential between auth errors and 404 confirms existence. This is the foundation of IDOR (Insecure Direct Object Reference) enumeration.


Forcing 401 Unauthorized

(Applies to: All methods) — Non-destructive when auth fails before any state mutation.

Mechanism: Per RFC 9110 §15.5.2, a server returns 401 when the request lacks valid authentication credentials. The oracle depends on evaluation order:

  • Auth-first (no oracle): Server checks credentials before resolving the resource. All unauthenticated requests get 401 regardless of resource existence.
  • Resource-first (oracle): Server resolves the resource before checking credentials. Existing resources trigger 401; non-existing resources return 404.

Isolated Variable: Only the authentication credential is omitted or invalidated. Everything else matches a legitimate request.

Oracle Signal: 401 (exists, auth required) vs 404 (does not exist).

GET — Existing Resource (No Auth)

GET /api/users/1001/profile HTTP/1.1
Host: target.com

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Content-Type: application/json

{"error": "Unauthorized", "detail": "Authentication required"}

GET — Non-Existing Resource (No Auth)

GET /api/users/9999/profile HTTP/1.1
Host: target.com

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "Not Found"}

PUT — Existing Resource (Invalid Token)

PUT /api/users/1001/profile HTTP/1.1
Host: target.com
Authorization: Bearer invalid-token-value
Content-Type: application/json
Content-Length: 16

{"name": "test"}

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
{"error": "Unauthorized", "detail": "Invalid or expired token"}

PUT — Non-Existing Resource (Invalid Token)

PUT /api/users/9999/profile HTTP/1.1
Host: target.com
Authorization: Bearer invalid-token-value
Content-Type: application/json
Content-Length: 16

{"name": "test"}

HTTP/1.1 404 Not Found
{"error": "Not Found"}

💡 WWW-Authenticate header as a secondary oracle: Even when the status code is uniformly 401, the WWW-Authenticate response header may vary. Some servers include resource-specific realm values, supported auth schemes, or error descriptions that differ based on whether the resource was resolved.

💡 Expired vs missing vs malformed tokens: Test all three credential states. Some servers handle them at different pipeline stages: missing tokens may be rejected at the gateway (uniform 401, no oracle), while expired or malformed tokens may reach the application layer where resource resolution happens first.

Mitigation: Always validate authentication credentials before resolving the target resource. Return 401 for all unauthenticated requests regardless of whether the resource exists.


Forcing 403 Forbidden

(Applies to: All methods) — Non-destructive, since the server refuses to execute the operation.

Mechanism: Per RFC 9110 §15.5.4, a server returns 403 when it understands the request but refuses to authorize it. The server must find the resource and load its ACL before it can determine that the requester lacks permission. If the resource doesn't exist, there's no ACL to evaluate, so the server returns 404. This is the classic IDOR enumeration pattern.

Isolated Variable: The request uses valid credentials belonging to a low-privilege user or a user who is not the resource owner. The request is otherwise perfectly formed.

Oracle Signal: 403 (exists, no permission) vs 404 (does not exist).

GET — Existing Resource (Wrong User)

GET /api/users/1001/medical-records HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...low-privilege-user-token

HTTP/1.1 403 Forbidden
{"error": "Forbidden", "detail": "You do not have permission to access this resource"}

GET — Non-Existing Resource (Wrong User)

GET /api/users/9999/medical-records HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...low-privilege-user-token

HTTP/1.1 404 Not Found
{"error": "Not Found"}

DELETE — Existing Resource (Insufficient Role)

DELETE /api/admin/reports/rpt-500 HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...regular-user-token

HTTP/1.1 403 Forbidden
{"error": "Forbidden", "detail": "Admin role required"}

DELETE — Non-Existing Resource (Insufficient Role)

DELETE /api/admin/reports/rpt-999 HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...regular-user-token

HTTP/1.1 404 Not Found
{"error": "Not Found"}

POST — Existing Parent (Cross-Tenant)

POST /api/orgs/org-abc/invites HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...user-in-different-org-token
Content-Type: application/json

{"email": "new@example.com"}

HTTP/1.1 403 Forbidden
{"error": "Forbidden", "detail": "You are not a member of this organization"}

vs. 404 when org org-abc does not exist.

💡 The RFC endorses using 404 to hide existence: RFC 9110 §15.5.4 explicitly states: "If the server does not wish to make this information available to the request client, the status code 404 (Not Found) can be used instead." When a server returns 403, it is choosing to reveal existence.

💡 Error message as a permission-model oracle: The 403 response body often leaks details about the permission model — required roles, ownership constraints, or organizational boundaries. Compare response bodies across resource IDs to map the permission topology.

💡 Horizontal vs vertical privilege oracles:

  • Horizontal: Same role, different resource owner. User A queries User B's resource → 403. User A queries non-existent user's resource → 404. Confirms which user IDs have data.
  • Vertical: Lower role, higher-role resource. Regular user queries admin endpoint → 403. Regular user queries non-existent admin resource → 404. Confirms which admin resources exist.

Mitigation: Return 404 instead of 403 for resources the requester is not authorized to access — as explicitly permitted by RFC 9110. Never include resource-specific details in the 403 body.


Forcing Auth Differential via Token Scope Manipulation

(Applies to: All methods) — Non-destructive.

Mechanism: OAuth 2.0 tokens carry scopes (permissions). The oracle emerges when scope validation happens after resource resolution: the server loads the resource, checks the token's scopes, and returns 403 (insufficient scope) if they don't match. For a non-existing resource, the server returns 404 before ever inspecting scopes.

Isolated Variable: The token is valid and properly signed, but its scope is deliberately limited. Only the scope claim changes.

Oracle Signal: 403 with scope error (exists, scope insufficient) vs 404 (does not exist).

GET — Existing Resource (Wrong Scope)

GET /api/users/1001/billing HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...scope-users-read-only

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="api", error="insufficient_scope", scope="billing:read"
{"error": "Forbidden", "detail": "Token scope 'users:read' insufficient; requires 'billing:read'"}

GET — Non-Existing Resource (Wrong Scope)

GET /api/users/9999/billing HTTP/1.1
Host: target.com
Authorization: Bearer eyJhbG...scope-users-read-only

HTTP/1.1 404 Not Found
{"error": "Not Found"}

💡 Scope enumeration as a bonus oracle: When the 403 response includes the required scope, it leaks not just resource existence but the specific permissions needed to access it. Comparing required scopes across resource IDs reveals the resource's type or classification.

💡 Chaining with other techniques: Auth elicitation is often the first step in a multi-technique oracle chain. Once 401/403 confirms a resource exists, apply header-based, payload-based, or state-conflict techniques to extract additional information.

Mitigation: Validate token scopes at the middleware layer before resource resolution, or return uniform 404 for any request where the token lacks the required scope.

On this page