1

Vulnerability

User-to-Admin Access

⚠️

Vulnerability

User-to-admin access occurs when a user authenticated at a lower privilege level can reach functionality or data reserved for a higher-privilege role, such as an administrator.

For example:

Logged in as standard user:

GET /admin/dashboard
→ intended only for admin users
→ access granted ✗

The important difference from horizontal authorization is the boundary being crossed:

Horizontal
→ user 4521 → user 4522
→ same privilege level

Vertical
→ standard user → administrator functionality
→ different privilege levels

A vulnerable application might perform only the authentication check:

Valid session? ✓
        ↓
Serve /admin/dashboard

when it should also verify the required privilege:

Valid session? ✓
        ↓
Role/permission allows admin dashboard? ✗
        ↓
Reject

The bypass can happen in several ways:

Missing server-side role check
→ endpoint accepts any authenticated user

Client-side-only restriction
→ UI hides admin functionality but API doesn't enforce it

Incorrect role check
→ checks "logged in" instead of "admin"

Inconsistent authorization
→ /admin/dashboard protected
→ /api/admin/dashboard forgotten

Client-controlled role
→ server trusts role=admin from the request

The most important principle is that the privilege decision must be made server-side for the requested operation, not inferred from which page the user reached or what the client claims their role is.

2

Vulnerability

Role Manipulation

⚠️

Vulnerability

Role manipulation is a testing technique where the tester changes a client-controlled value that the application uses to determine the user's role or permissions, then observes whether the server accepts the modified privilege.

Common locations include:

JWT payload:
{ "sub": "user_4521", "role": "user" }
→ attempt to change role → "admin"

Hidden form field:
<input type="hidden" name="role" value="user">
→ change to → "admin"

Request body:
{ "username": "bob", "role": "user" }
→ change to → { "username": "bob", "role": "admin" }

Does the server independently establish the user's real privileges, or does it trust what the client claims?

JWT claims

A role inside a JWT can be perfectly legitimate when the token is properly signed and validated:

JWT
→ signature verified ✓
→ issuer/audience validated ✓
→ trusted role claim accepted ✓

It becomes dangerous when the application:

fails to verify the signature
OR
accepts a client-created token
OR
allows the client to choose the role claim

In those cases, changing:

"role": "user"

to:

"role": "admin"

could produce an unauthorized privilege escalation.

Hidden fields and request parameters

The same principle applies to ordinary request data:

POST /register
{
  "username": "bob",
  "role": "admin"
}

If the server blindly maps every incoming field onto a user or session object, the attacker may be able to assign themselves privileges they were never supposed to have.

This is closely related to mass assignment:

Client-controlled field
        ↓
Automatic object binding
        ↓
Sensitive field modified
        ↓
Privilege escalation

The secure design is to use an explicit allow-list and derive sensitive attributes from trusted server-side state:

Request
→ username, email, password
→ accepted

role
→ ignored / server-assigned

The same principle applies after login: the server should determine the user's effective permissions from trusted state, rather than allowing the client to assert "role": "admin".

3

Vulnerability

Administrative Endpoint Access

⚠️

Vulnerability

Administrative endpoint access occurs when a privileged endpoint exists and is reachable, but the server relies on the endpoint being hidden or unknown instead of explicitly verifying that the requester has administrative permission.

For example:

POST /admin/users/promote
→ not linked for normal users
→ endpoint still exists
→ standard user calls it directly
→ request succeeds ✗

The application may correctly hide the functionality:

Admin
→ "Promote User" button visible

Standard user
→ button hidden

But that provides no security boundary if the underlying endpoint accepts the request anyway.

An attacker might discover the endpoint through:

JavaScript bundles
→ exposed route names

API specifications
→ accidentally public documentation

Predictable naming
→ /user/... → /admin/...

Application behavior
→ links or redirects revealing paths

Guessing
→ conventional administrative endpoints

The critical distinction is:

Endpoint not linked
        ≠
Endpoint protected

A secure server should independently enforce the required permission:

POST /admin/users/promote
        ↓
Authenticate requester
        ↓
Check required admin permission
        ↓
Allow / Deny

This also means that discoverability is not the vulnerability itself. Even if an attacker knows the exact endpoint, a correctly authorized application should simply reject the request:

Standard user
→ discovers /admin/users/promote
→ sends request
→ server checks permission
→ 403 Forbidden

A URL being difficult to discover is not an authorization mechanism. Every administrative endpoint must enforce its privilege requirement server-side, regardless of whether the endpoint is exposed through the UI.

4

Vulnerability

Function-Level Access Control

⚠️

Vulnerability

Function-level access control is the broader principle that authorization must apply to the specific action being performed, not merely to the page or route through which that action is normally reached.

A common mistake is protecting the administrative page while assuming everything behind it automatically inherits that protection:

GET /admin/users
→ requires admin ✓

deleteUser(userId)
→ performs the actual deletion
→ no independent authorization check ✗

POST /api/users/delete
→ calls deleteUser()
→ reachable without the admin page
→ deletion succeeds ✗

The attacker doesn't need to access /admin/users at all. They can invoke the underlying operation through another endpoint or request path.

The dangerous assumption is:

"Only admins can reach this page"
        ↓
"Therefore only admins can perform these actions"

Those are not equivalent.

The server needs to establish authorization at the point where the sensitive operation is actually performed:

Request
  ↓
Authenticate
  ↓
Authorize requested action
  ↓
Perform operation

For example:

POST /api/users/delete
        ↓
Is requester authenticated? ✓
        ↓
Does requester have permission to delete users? ✗
        ↓
Reject

This applies to any sensitive operation:

deleteUser()
changePrice()
issueRefund()
promoteUser()
changePermissions()
exportData()

The key distinction is:

Page-level authorization
→ "Can this user access this page?"

Function-level authorization
→ "Can this user perform this specific action?"