Lesson 5.2: Broken access control and privilege escalation
IDOR (Lesson 5.1) is a missing check at the object level: you can see another person’s data. Broken access control here refers to the function level: you can reach a function that only an admin should use. OWASP puts both in the A01:2021 Broken Access Control group, which sits at the top of the risk ranking. This lesson walks through the three most common function-level flaws and escalates a normal user to admin on a real lab by three different routes.
Forced browsing, a client-supplied role flag, and verb tampering all reach the same admin function.
Level: medium, about 35 minutes. Prerequisites: Lesson 5.1 (IDOR). Tools: Burp Suite, ffuf/dirsearch, curl.
Goals
After this lesson you understand Broken Access Control (BAC) at the function level, you recognize forced browsing, trusting a client-supplied flag, and verb/method tampering, you can escalate from a normal user to admin on a real lab, and you know the principle of a centralized, deny-by-default authorization check.
Theory
IDOR (Lesson 5.1) is a missing check at the object level: you can view another person’s data. Broken access control here means the function level: you can reach a function that only an admin should be able to use. OWASP groups both under A01:2021 Broken Access Control, the group at the top of the risk ranking.
This lesson goes through the three most common flaws.
Forced browsing
A developer sets up an admin page or endpoint but hides it by not linking it anywhere, then forgets the authorization check entirely. The user only has to guess the right path to get in:
1
/admin, /admin/panel, /backup/users.json, /debug, /.git/config
“No link” is not access control. An attacker uses a wordlist with ffuf or dirsearch to find these paths in seconds.
Trusting a flag from the client
The app decides authorization from a value the client sends and can change:
1
2
3
Cookie: role=user -> change to role=admin
Hidden field: isAdmin=0 -> change to isAdmin=1
Header: X-Role: user -> change to X-Role: admin
Authorization must be derived from the login session on the server side (look up in the database what role this user has), never trust a value the client declares itself.
Verb tampering and flow tampering
The authorization check is attached to the wrong step or the wrong HTTP method, while the real action goes through a different step or method:
- Blocking GET /admin but forgetting POST, or the other way around.
- Checking authorization at the step that shows a confirmation form, but not at the step that executes the action.
- Honoring override headers such as
X-Original-URL,X-Rewrite-URL,X-HTTP-Method-Override, which lets an attacker slip past a rule that blocks by path or method.
The principle: the authorization check must sit at the step that executes the action, and apply to every method.
Demo
The workflow to find BAC:
- Map the functions with two accounts: one admin, one normal user. Write down the requests only the admin can call.
- Use ffuf to probe hidden paths:
1
ffuf -u http://127.0.0.1:8051/FUZZ -w wordlist.txt -mc 200,302
- Take an admin request and replay it with the normal user’s session (or with none) in Burp Repeater. If it still works, that is BAC.
- Try changing the authorization cookie/header, and try changing the method (GET to POST and back).
An example of trusting a client flag with curl:
1
2
3
$ curl -s localhost:8051/admin/panel --cookie 'role=admin'
{"flag":"FLAG{5.2_tin_role_tu_client}","ok":true,"panel":"admin",
"users":["admin","carol","dave"]}
Just by setting the cookie role=admin, a normal user walks straight into the admin page.
Lab
- Vulnerable app:
labs/5.2/(runs locally, port 127.0.0.1:8051). Accountcarol:pw(a normal user). - Task: collect all three flags, each matching one flaw.
- Forced browsing:
GET /backup/users.json(no login needed). - Trusting the client role: set the cookie
role=adminto reach/admin/panel. - Verb tampering: POST directly to
/admin/promoteto promote yourself to admin.
- Forced browsing:
- Hint: look closely at
/admin/promote, where the GET step checks authorization and the POST step does not. - Solution:
labs/5.2/exploit.pyandlabs/5.2/transcript.txt.
Excerpt from a real run:
1
2
3
4
5
6
[*] Buoc 3: verb tampering - carol (role=user) POST promote:
POST /admin/promote user=carol -> 200 {'flag': 'FLAG{5.2_verb_tampering_bo_qua_kiem_quyen}', ...}
[+] Flags thu duoc:
FLAG{5.2_forced_browsing_khong_kiem_quyen}
FLAG{5.2_tin_role_tu_client}
FLAG{5.2_verb_tampering_bo_qua_kiem_quyen}
Common pitfalls
- Hiding the admin button in the UI while the backend still serves the request.
- Blocking by exact path string (for example blocking “/admin”) but missing “/Admin”, “/admin/”, “/admin;/…”, or an API endpoint not under /admin.
- Placing the authorization check on GET while the real action lives in POST.
- The backend honoring a proxy’s URL/method override headers without careful configuration.
- Authorization scattered across many places, so sooner or later one spot is missed.
Further reading
- PortSwigger Web Security Academy: Access control vulnerabilities (labs on unprotected admin, role tampering, method-based access).
- OWASP Top 10 A01:2021 Broken Access Control.
- OWASP API Security Top 10: API5 Broken Function Level Authorization (BFLA).
Key takeaways
- Function-level BAC: you can reach an admin function that is not yours to use.
- Always test with two accounts (admin and normal user), and replay the admin’s request with the user’s session.
- Probe for forced browsing with a wordlist; hiding a link is not security.
- Never trust the role or authorization sent from the client.
- Check authorization for every method and at the step that executes the action, not just the display step.
- Defense: deny by default, and gather the authorization logic in one place (middleware or a decorator).
