Privilege Escalation
Horizontal: see another user data. Vertical: become an admin. Both happen when the server trusts a client-supplied role flag or fails to enforce per-action checks.
Horizontal: see another user data. Vertical: become an admin. Both happen when the server trusts a client-supplied role flag or fails to enforce per-action checks.
Privilege escalation occurs when a user gains access to resources or functionality that their role should not permit. It comes in two flavours: horizontal— accessing another user's data at the same privilege level — and vertical — elevating to a higher role such as admin or super-user.
Horizontal escalation is about scope— the attacker stays at their privilege level but accesses data that belongs to someone else. A support agent viewing another agent's tickets by changing ?ticket_id=45 to ?ticket_id=46 is a horizontal escalation.
Vertical escalation is about level — the attacker performs an action that requires a higher privilege. A standard user adding role=admin to their profile update request and gaining access to the admin panel is a vertical escalation. Both stem from the same root cause: the server trusts the client to declare who they are or what they can access.
The sandbox below simulates a helpdesk support portal. You are logged in as Alice (Support Agent). Try changing the ticket ID in the URL to see other agents' tickets (horizontal escalation), or change your role parameter to access the admin panel (vertical escalation). Toggle the auth check to see how server-side validation blocks both vectors.
In 2021, GitLab patched a critical vulnerability in their GraphQL API that allowed any authenticated user to access private project data belonging to other users. The bug was a classic horizontal escalation: the GraphQL resolver accepted a project ID without verifying that the requesting user was a member of that project. Attackers could iterate through project IDs and exfiltrate source code, CI/CD variables, and issue tracker data from private repositories.
The incident demonstrates how privilege escalation vulnerabilities can exist even in modern API patterns like GraphQL. The resolver was the authorisation boundary, but it lacked an ownership check. GitLab fixed it by adding project membership verification in the resolver layer.
The fix for privilege escalation is simple in concept but requires discipline: every endpoint that accesses a resource must verify that the authenticated user is authorised for that specific resource and action. Never derive privilege from client-supplied parameters.
// VULNERABLE - trusts user-supplied role and ID
app.get('/api/tickets/:id', async (req, res) => {
const ticket = await db.query(
'SELECT * FROM tickets WHERE id = $1',
[req.params.id],
);
res.json(ticket.rows[0]);
});
app.patch('/api/profile', async (req, res) => {
// Accepts role from request body!
await db.query(
'UPDATE users SET name = $1, role = $2 WHERE id = $3',
[req.body.name, req.body.role, req.session.userId],
);
res.json({ ok: true });
});
// SAFE - enforce ownership and role from session
app.get('/api/tickets/:id', async (req, res) => {
const ticket = await db.query(
`SELECT * FROM tickets
WHERE id = $1 AND assigned_agent_id = $2`,
[req.params.id, req.session.userId],
);
if (!ticket.rows[0]) return res.status(403).end();
res.json(ticket.rows[0]);
});
app.patch('/api/profile', async (req, res) => {
// Role is never read from the body
const { name, email } = req.body;
await db.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3',
[name, email, req.session.userId],
);
res.json({ ok: true });
});Use a centralised authorisation middleware that maps each route to the required permissions. Avoid checking roles inline — a reusable gate function (like requireRole('admin')) makes it harder to miss a check when new endpoints are added.
1.What is the difference between horizontal and vertical privilege escalation?
2.A profile update endpoint accepts { name, email, role } in the request body. What is the risk?
3.A developer adds a new API endpoint but forgets to include the authorisation check. What is the likely outcome?