Privilege escalation: horizontal and vertical
Applications have different kinds of users: visitors, customers, moderators, administrators. Each role should be able to do certain things and nothing more. Privilege escalation means doing something that belongs to another account or to a higher role. In this lesson you learn to distinguish two directions, sideways and upward, and how to test both.
Lab only
Accessing an administration interface or using functions you are not entitled to is an intrusion on a real system, even if no password was required to reach the page. In a professional test, you use the accounts you are given at each privilege level and you do not create admin accounts or change real settings unless explicitly authorised. In this lesson, test your own training application.
Two directions
| Horizontal escalation | Vertical escalation | |
|---|---|---|
| Direction | Sideways: same privilege level, another user’s data | Upward: a higher privilege level |
| Example | A customer reads another customer’s invoices | A customer uses an administrator function |
| Typical cause | Missing object-level check (IDOR) | Missing function-level check, trusting client-side role data |
The previous lesson’s IDOR is the classic case of horizontal escalation. Here we concentrate on the vertical kind, and on the techniques that testers use to organise their checks across roles.
Principle: authorisation is enforced on the server
Hiding a button is not access control. Many applications show the admin menu only to administrators and call it done. But the functions behind that menu are ordinary requests to ordinary URLs. If the server does not verify the role when that request arrives, anyone who knows or guesses the URL (or just finds it in a JavaScript file) can use it.
Think of a building where the staff-only door has no sign on it. That is not security by obscurity, it is nothing: the real protection is the lock, and a lock is a server-side check.
Function-level access control failures
Forced browsing
Your mapping from chapter 1 may have revealed admin-looking paths in JavaScript files, in robots.txt or in the site map: /admin, /manage/users, /api/admin/export. Request them with an ordinary user’s session. A secure application answers with 403 or 404. A vulnerable one displays the page or accepts the call.
Different method, different check
Developers sometimes protect GET but forget POST, PUT or DELETE on the same path. Trying other methods on a protected endpoint is an easy test, in Repeater.
Parameters that carry the role
Some applications store the role or privilege level where the user can alter it: a cookie (role=user), a hidden form field, a JSON field in a profile update, or a token whose contents are not verified. If the server believes this value, changing it changes the privilege. Whenever you see a value that looks like a role, flag, level or group in a request, note it and test whether the server trusts it.
Mass assignment
Frameworks often offer a convenient feature that binds all incoming fields to an object automatically. If a profile update endpoint accepts {"name":"Alice"} but would equally accept {"name":"Alice","isAdmin":true}, then users can set fields that were never meant for them. The fix is to explicitly list the fields allowed for each operation.
// Vulnerable idea: everything in the request goes into the user object
user.update(request.body)
// Safer: only allowed fields are copied
user.name = request.body.name
user.email = request.body.email
Access through alternative paths
A page may be protected, while the API it uses is not. A web interface may be restricted, but the mobile API behind it may accept the same action without a role check. Test the same action through each interface you identified.
The role matrix test
Here is where your role matrix from chapter 1 pays off. The systematic technique is:
- Log in with each role you have an account for (visitor with no session, customer, admin if provided).
- With the most privileged account, browse every feature while Burp records the requests. You now have a list of all privileged requests.
- For each privileged request, replay it in Repeater with the session of a lower-privileged user, and with no session at all.
- Record the result in the matrix: allowed or refused.
| Request | Admin session | Customer session | No session |
|---|---|---|---|
| GET /admin/users | 200 | 403 | 302 to login |
| POST /admin/users/delete | 200 | 200 (!) | 302 to login |
| GET /api/export | 200 | 200 (!) | 401 |
The cells marked with an exclamation point are findings. This table is quick to build, convincing in a report, and very hard for developers to dispute. In a real engagement, make sure the replayed requests do not cause harmful side effects, and for destructive actions like “delete user”, use test objects created for that purpose.
Multi-step processes
Access control must hold at every step of a workflow, not only the first. If a wizard has three pages and only the first checks the role, a user may jump directly to step three. The same problem appears when a process is protected by hiding links but each step’s endpoint is open. When mapping, record the sequence of requests, and test steps out of order and without the previous ones.
Beyond web roles: the wider idea
The word “privilege escalation” is also used for operating systems: a regular user becoming an administrator on a machine. You touched on this in the previous course. The pattern is the same everywhere: a boundary exists between privilege levels, and a flaw lets someone cross it. In the MITRE ATT&CK knowledge base at attack.mitre.org, privilege escalation is one of the tactics that describe what attackers try to achieve after gaining a first foothold.
Prevention
- Enforce authorisation on the server for every request, for pages and APIs, for all HTTP methods.
- Deny by default and grant roles the minimum permissions (least privilege).
- Centralise access control in a framework or middleware, instead of re-implementing it per endpoint.
- Never trust client-side values for role, price, owner or status; keep those in the server’s session or database.
- Use allowlists for assignable fields to prevent mass assignment.
- Log access control failures and alert on repeated denials, since they may indicate probing.
- Test authorisation automatically: for each endpoint, call it as every role and confirm the expected result.
Tip
The phrase “an admin page that is not linked anywhere” is a warning sign in your report, not a defence in theirs. Obscure URLs are found through JavaScript files, archived pages, and logs; only a server-side check protects the page.
Your turn: build a role matrix
In your lab application, with at least two roles (for instance DVWA’s admin and a regular user, or Juice Shop’s admin and a new customer), build a table with at least six privileged requests and the result for each role and for no session. Mark each unexpected success and write the recommendation.
See a solution
You should obtain a table similar to the one earlier in this lesson. For every unexpected success, a finding sheet reads:
Finding: missing function-level access control on user deletion
Evidence: a customer session received 200 on the admin delete request and the test user was removed
Impact: any logged-in user can delete other accounts
Recommendation: verify the administrator role on the server for this route and all methods;
centralise the check; add an automated test per role
If you find nothing in your lab, that is a result too: write “no escalation found in the tested requests” and list what you tested, so that the coverage is clear.
Key takeaways
- Horizontal escalation reaches other users’ data at the same level; vertical escalation reaches functions of a higher role.
- Hiding buttons or links is not access control: the server must check the role on every request.
- Test forced browsing, other HTTP methods, role values in requests, mass assignment and alternative interfaces such as APIs.
- Build a role matrix: replay privileged requests with lower-privilege and no sessions.
- Prevent with deny-by-default, centralised checks, least privilege and server-side-only trust.
Next, we explore a more subtle family of flaws, where every individual check passes yet the application still does something wrong: business logic flaws.