User accesses resources belonging to a higher privilege level by manipulating access control parameters in the request.
TL;DR
Vertical privilege escalation via IDOR occurs when a user at a lower privilege level accesses endpoints, functions, or data restricted to users at a higher privilege level — administrators, managers, operators, or internal service accounts. The attack crosses a role boundary: the attacker does not merely access a peer's data (horizontal IDOR) but invokes capabilities or accesses objects that the authorization model explicitly reserves for elevated roles.
In the API security context, this subtype is classified as BFLA — Broken Function Level Authorization — and holds position API5:2023 in the OWASP API Security Top 10 (CWE-285: Improper Authorization). The root cause is identical to horizontal IDOR: authentication confirms identity but authorization does not enforce the role constraint at the API layer. Admin controls are implemented in the UI (the menu item is hidden), but the underlying API routes lack role enforcement middleware.
The CVSS range for vertical escalation is consistently higher than horizontal IDOR: 7.5–9.1. Because admin endpoints often expose user management, system configuration, financial data, and infrastructure controls, successful vertical escalation typically yields system-wide compromise rather than a single user's data. CVE-2025-27507 in the ZITADEL identity platform — CVSS 9.0, Critical — demonstrates the endpoint: a regular user calling an admin LDAP configuration endpoint achieved full account takeover for every LDAP user on the entire instance.
Three distinct attack vectors produce vertical privilege escalation:
The most common pattern: admin API routes exist and are callable by authenticated regular users because the role check was implemented in the UI but not the API middleware.
GET /api/admin/users HTTP/1.1
Host: target.com
Authorization: Bearer <regular_user_token>Expected: 403 Forbidden. If the server returns 200 OK with a list of all users, vertical IDOR is confirmed. Admin endpoints protected only by UI visibility are systematically discovered via wordlist fuzzing or API documentation review.
Multiple frameworks implement HTTP method override for legacy client compatibility. A POST request with the right header is processed as DELETE, PUT, or PATCH — and the authorization check applies to the declared method, not the effective one:
POST /api/users/1338 HTTP/1.1
Host: target.com
Authorization: Bearer <regular_user_token>
X-HTTP-Method-Override: DELETEKnown override headers, all of which must be tested:
X-HTTP-Method-Override # Rails, Django REST Framework, Spring
X-HTTP-Method # ASP.NET, older Express
X-Method-Override # Laravel, custom middleware
X-Original-Method # Nginx proxy / Java EE
_method # Query param — Rails, Rack: POST /users/1?_method=DELETEVersioned APIs frequently add authorization middleware to newer versions while older versions remain deployed for backward compatibility. The v1 endpoint may handle the same business logic with the v2 controller but without the authorization layer added later:
GET /api/v2/admin/users → 403 Forbidden (authorization middleware present)
GET /api/v1/admin/users → 200 OK (legacy route lacks middleware)| Variant | Technique | Impact |
|---|---|---|
| Direct admin endpoint | Call /admin/* with regular user token | User management, system config |
| Method override | POST + X-HTTP-Method-Override: DELETE | Destructive operations on any resource |
| Version downgrade | /api/v1/ vs /api/v2/ middleware gap | Admin function bypass |
| Role parameter injection | PATCH /users/me with {"role": "admin"} | Permanent privilege escalation |
| Admin object reference | GET admin report ID discovered via recon | Sensitive internal data |
| Function-level access | Invoke admin actions (ban user, reset password) | Account takeover, data destruction |
CVE-2025-27507 — ZITADEL Identity Platform (CVSS 9.0, Critical, 2025) — Discovered by Amit Laish (GE Vernova). ZITADEL's Admin API exposed 12 HTTP endpoints — including /idps/ldap and /idps/ldap/{id} — that enforced org-level IAM permissions instead of the required system-level permissions. Incorrect scoping in the gRPC service definitions meant authenticated non-admin users could call these endpoints with their standard bearer token. The attack chain: call the LDAP configuration endpoint → redirect LDAP authentication to a malicious server → intercept LDAP credentials on next login → achieve full account takeover for all LDAP-authenticated users on the instance. Affected eight version branches across ZITADEL 2.x. Advisory: GHSA-f3gh-529w-v32x.
HackerOne #415081 — PayPal ($10,500, Vertical IDOR) — IDOR on the PayPal business user management API /businessmanage/users/api/v1/users allowed parameter manipulation to grant secondary account creation rights on victim business accounts. A regular authenticated PayPal user performed an administrative operation — adding a secondary user to another account — that is restricted to the account owner. The bounty reflects the business impact: account takeover on PayPal business accounts.
CVE-2024-7297 — Langflow Mass Assignment Vertical Escalation (CVSS 8.8, July 2024) — Langflow versions below 1.0.13 accepted {"is_superuser": true} in a PATCH /api/v1/users/<USER_ID> request from any authenticated user. The server applied the field without validation, immediately granting super-admin access to all flows, credentials, and infrastructure. Discovered by Tenable Research (TRA-2024-26).
CVE-2026-29056 — Kanboard User Invite (High) — The UserInviteController::register() method passed all POST parameters directly to UserModel::create() without filtering the role field. An email-invite recipient could register as role=app-admin via the invite registration flow. The standard account settings controller correctly allowlisted fields — only the invite path was vulnerable, demonstrating the inconsistency-across-code-paths pattern that creates vertical IDOR.
Construct a privilege matrix for the application, then test every combination systematically:
| Endpoint | Unauthenticated | Regular User | Admin | Expected |
|---|---|---|---|---|
GET /api/users/me | 401 | 200 | 200 | Both roles OK |
GET /api/admin/users | 401 | 403 | 200 | Regular must be blocked |
DELETE /api/users/1338 | 401 | 403 | 200 | Regular must be blocked |
POST /api/admin/reset-password | 401 | 403 | 200 | Regular must be blocked |
GET /api/reports/financial | 401 | 403 | 200 | Regular must be blocked |
Any cell in the "Regular User" column that returns 200 when the expected result is 403 is a confirmed BFLA.
# Discover admin endpoints via wordlist
ffuf -u https://target.com/api/FUZZ \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/api-endpoints.txt \
-H "Authorization: Bearer $REGULAR_USER_TOKEN" \
-mc 200 -fs 0
# Test HTTP method override on discovered endpoints
curl -X POST https://target.com/api/users/1338 \
-H "Authorization: Bearer $REGULAR_USER_TOKEN" \
-H "X-HTTP-Method-Override: DELETE" -v
# API version downgrade sweep
for version in v1 v2 v3 v4; do
echo "Testing /api/$version/admin/users:"
curl -s -H "Authorization: Bearer $REGULAR_USER_TOKEN" \
"https://target.com/api/$version/admin/users" | head -c 200
doneBreachVex tests vertical IDOR by probing admin-matching URLs (matching patterns such as admin, manage, settings, internal, backoffice, system, config, moderation, staff, superuser) with a regular user token, falling back to a curated admin-path wordlist when recon finds no admin endpoints. False positives are suppressed by a static-asset guard that blocks .html/.css/.js files, a soft-404 gate that eliminates custom error pages returned as HTTP 200, and an admin content-marker requirement (admin, dashboard, user management) on wordlist probes.
BreachVex also probes PUT/DELETE/PATCH on GET-only API endpoints, using OPTIONS to discover declared methods first.
Front-end access control is not access control. Hiding an admin menu item, disabling a button, or graying out a UI element has zero security value — any client can issue raw HTTP requests. The only security boundary is the API layer. Every admin endpoint must enforce role checks in server-side middleware, independent of what any client UI presents.
# FastAPI — role enforcement decorator
from functools import wraps
from fastapi import HTTPException, Depends
def require_admin(user: User = Depends(get_current_user)):
if user.role != "admin":
raise HTTPException(status_code=403, detail="Admin access required")
return user
# Applied to every admin route — cannot be omitted at route definition
@router.get("/api/admin/users")
async def list_all_users(admin: User = Depends(require_admin)):
return await db.users.find_all()
@router.delete("/api/admin/users/{user_id}")
async def delete_user(user_id: int, admin: User = Depends(require_admin)):
await db.users.delete(id=user_id)// Express — role middleware applied at router level
const adminRouter = express.Router();
// One middleware applied to all routes on this router
adminRouter.use((req, res, next) => {
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
next();
});
adminRouter.get('/users', listAllUsersHandler);
adminRouter.delete('/users/:id', deleteUserHandler);
// Mount with restricted path — every route under /api/admin is protected
app.use('/api/admin', authenticate, adminRouter);# Django REST Framework — disable method override globally
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [...],
# Do not include method override middleware unless explicitly required
}
# If method override is needed for specific legacy clients, restrict by route:
# Apply X-HTTP-Method-Override only to endpoints that legitimately need it# FastAPI — shared authorization dependency applied to all versions
admin_required = Depends(require_admin)
# Apply the same dependency to both v1 and v2 admin routes
@router_v1.get("/admin/users", dependencies=[admin_required])
@router_v2.get("/admin/users", dependencies=[admin_required])
async def list_users():
return await db.users.find_all()
# Both versions enforce identical authorizationVertical privilege escalation via IDOR occurs when a regular user accesses endpoints, functions, or data restricted to higher-privilege roles — admin, manager, operator — by directly referencing admin objects or calling admin-level functions without the appropriate role. Unlike horizontal escalation (peer access), vertical escalation involves a role boundary crossing.
BFLA (Broken Function Level Authorization) is OWASP API5:2023 — it occurs when a user invokes a function or HTTP method they lack the privilege to call. BOLA (API1:2023) is about accessing a peer's object. BFLA is vertical: the attacker calls DELETE /admin/users with a regular user token. BOLA is horizontal: the attacker calls GET /api/orders/{peer_id} with their own token.
CVE-2025-27507 (CVSS 9.0) in the ZITADEL identity platform allowed authenticated non-admin users to call 12 Admin API gRPC endpoints — including LDAP configuration modification — due to incorrect IAM permission scoping. Attackers redirected LDAP authentication to a malicious server, intercepting credentials and achieving full account takeover for all LDAP users on the instance.
HTTP method override headers (X-HTTP-Method-Override, X-HTTP-Method, _method query param) allow clients to specify an alternate HTTP method. Rails, Django REST Framework, Spring, and Express all implement this for legacy client compatibility. If the server processes a POST with X-HTTP-Method-Override: DELETE as a DELETE, a regular user may be able to perform destructive operations they would be blocked from doing directly.
A versioned API may add authorization middleware in v2 while v1 remains accessible without that middleware. An attacker who gets 403 on GET /api/v2/admin/users may get 200 on GET /api/v1/admin/users. Both versions hit the same underlying handlers but only v2 has the authorization layer. This is a common legacy code pattern.
HackerOne report #415081 on the PayPal program involved IDOR on the business user management API. It allowed regular users to perform administrative actions — adding secondary users to other accounts — by manipulating identifiers in the request body. Bounty: $10,500 for privilege escalation via direct object reference.
Every admin endpoint must enforce role checks at the API layer — not the UI layer. Middleware should verify the session role is 'admin' or equivalent before any handler executes. Defense in depth: network-level IP allowlist for admin routes, separate admin API subdomain with stricter CSP, and audit logging for all admin actions.
A privilege matrix documents which roles may call which endpoints and methods. BFLA testing executes every role/endpoint combination and validates responses against the matrix. Without the matrix, coverage is incomplete — a tester might confirm that regular users can't list users but miss that they can delete users.