How I approached 13 real-world access control vulnerabilities using nothing but Burp Suite, curiosity, and a habit of checking things nobody else checks

If there’s one category of vulnerability that keeps showing up in real bug bounty reports and real breaches, it’s broken access control. Not because it’s technically hard to find — it’s usually the opposite. Most access control bugs are found by asking one simple question over and over: “What happens if I just… try this anyway?”
I spent time working through PortSwigger’s Web Security Academy access control labs, and instead of just collecting solved checkmarks, I kept notes on exactly what I looked at, what I changed, and why it worked. This article is those notes, cleaned up and turned into something you can actually learn from — whether you’re starting out in web security or brushing up before an assessment.
This is not a theory-heavy write-up. It’s a practical, step-by-step walkthrough of 13 labs, in the order I solved them, with the actual requests, headers, and parameters involved.
What Is Access Control?
Access control is the set of rules that decide who is allowed to do what inside an application. It answers questions like:
- Can this user view this page?
- Can this user perform this action?
- Can this user see or modify someone else’s data?
When access control is broken, a user ends up doing something they were never supposed to be able to do — viewing another user’s account, reaching an admin panel, or promoting themselves to a higher privilege level. The scary part is that these bugs often require no advanced exploit, no payload, and no fuzzing. Just a parameter change or a missing header check.
Authentication vs Authorization
These two terms get mixed up constantly, so here’s the short version:
- Authentication answers: “Who are you?” — this is your login step, your username and password, your session cookie.
- Authorization answers: “Are you allowed to do this?” — this is checked (or not checked) every single time you request a page or perform an action.
Here’s the practical example that matters: logging in as “wiener” proves who wiener is. It does not automatically mean wiener should be allowed to open /admin, delete other users, or view someone else's private data. A lot of applications get authentication right and completely forget to re-check authorization on every single request — and that gap is exactly what these labs are built to teach.
Burp Suite is the tool of choice for this kind of testing because access control bugs live inside the raw HTTP request — a parameter, a header, a cookie, a path — things that are easy to overlook in the browser UI but obvious once you’re looking at the request itself in Repeater.
How I Approach Access Control Testing
Across all 13 labs, my methodology stayed roughly the same:
- Read the lab description carefully — it usually hints at exactly what’s broken.
- Explore the application as a normal user first.
- Check for “hidden” clues: robots.txt, page source, JavaScript files.
- Note any interesting endpoints or parameters (id, admin, roleid, etc.).
- Capture requests in Burp Suite and read them carefully.
- Send interesting requests to Repeater.
- Modify parameters, headers, HTTP methods, or session cookies.
- Compare the response before and after the change.
- Confirm the actual impact (e.g., did the user actually get deleted/promoted).
Keep this checklist in mind — it’s the backbone of every lab below.
Lab 1 — Unprotected Admin Functionality
Vulnerability / Concept
The application has an admin panel that isn’t linked anywhere in the normal navigation, but it also isn’t protected by any real access control — it’s simply “hidden by obscurity.”

Lab Objective
Find the unlinked admin panel and use it to delete the user carlos.

What I Looked For
Since there was no visible link to an admin section anywhere on the site, the next logical place to check was robots.txt — a file that often lists paths the site owner doesn't want search engines to index, which frequently includes admin paths developers forgot were still sensitive.
Step-by-Step Walkthrough
Step 1 — Open the Lab and Explore
I opened the lab and clicked through every visible function on the site to get a feel for the normal user flow.

Step 2 — Check robots.txt
I navigated to /robots.txt and reviewed the Allow and Disallow entries.

Step 3 — Find the Hidden Path
The file disclosed a path: /administrator-panel.
Step 4 — Access the Admin Panel
I appended /administrator-panel to the site's base URL and loaded it directly in the browser.
Step 5 — Exploit the Access
The panel loaded with no authentication or authorization check at all. From there, I located the user carlos and deleted the account.

Lab solved.
Why This Works
The developer relied on the admin URL being “unguessable” as a security measure instead of enforcing a real authorization check (like verifying the logged-in user’s role) on the server side. robots.txt is public by design, which defeats the "security through obscurity" approach immediately.
Real-World Lesson
Never rely on a URL being secret. Any endpoint that performs a privileged action must verify — on the server, on every request — that the requesting user actually has permission to be there.
Lab 2 — Unprotected Admin Functionality with Unpredictable URL
Vulnerability / Concept
Same idea as Lab 1, but this time the admin path is a random-looking string instead of something guessable. The flaw isn’t in the randomness itself — it’s that the client-side JavaScript reveals the path.

Lab Objective
Find the admin panel path and delete carlos.
What I Looked For
Since robots.txt didn't reveal anything useful this time, I moved to inspecting the page's client-side code directly.
Step-by-Step Walkthrough
Step 1 — Open the Lab and Explore
I checked all visible functionality and also checked robots.txt again as a first pass (it came up empty this time).
Step 2 — View Page Source
I right-clicked on the page and selected “View Page Source” to read the raw HTML and any inline JavaScript.

Step 3 — Inspect the JavaScript
Inside the source, I found this block:
var isAdmin = false;
if (isAdmin) {
var topLinksTag = document.getElementsByClassName("top-links")[0];
var adminPanelTag = document.createElement('a');
adminPanelTag.setAttribute('href', '/admin-cc9idx');
adminPanelTag.innerText = 'Admin panel';
topLinksTag.append(adminPanelTag);
var pTag = document.createElement('p');
pTag.innerText = '|';
topLinksTag.appendChild(pTag);
}
This code only shows the admin panel link when isAdmin is true for the current user — but it still reveals the actual path (/admin-cc9idx) to anyone who reads the source, regardless of whether they're an admin.
Step 4 — Navigate to the Hidden Path
I copied /admin-cc9idx and appended it to the site's URL, then loaded it directly.

Step 5 — Exploit the Access
The admin panel loaded successfully. I located carlos and deleted the account.

Lab solved.
Why This Works
The server never actually enforces who can reach /admin-cc9idx — the "protection" only hides a link to it in the UI. Client-side logic (isAdmin in JavaScript) has zero enforcement power; it only controls what's rendered in the browser, not what the server will actually allow.
Real-World Lesson
Never make authorization decisions in client-side code. Anything shipped to the browser — JavaScript, HTML comments, hidden form fields — can be read by the user. Authorization checks must happen server-side, on every request.
Lab 3 — User Role Controlled by Request Parameter
Vulnerability / Concept
The application decides whether a user is an admin based on a parameter it trusts from the client (admin=false / admin=true), instead of checking the user's actual role on the server.

Lab Objective
Access the admin panel and delete carlos.
What I Looked For
The lab description made it clear that /admin existed but wasn't currently accessible. That pointed me toward reviewing the raw HTTP requests in Burp Suite rather than guessing paths in the browser.
Step-by-Step Walkthrough
Step 1 — Explore and Read the Description
I opened the lab, explored the functions, and read the lab description carefully — it mentioned an admin panel at /admin.
Step 2 — Check robots.txt and Page Source
I checked both as a first pass; neither revealed anything extra this time, so I moved on to Burp Suite.
Step 3 — Log In and Capture Requests
I logged in using the provided credentials and reviewed the requests flowing through Burp’s HTTP history.
Step 4 — Try the Admin Path Directly
I took the my-account request, sent it to Repeater, and changed the path from /my-account to /admin.
GET /admin HTTP/2
Host: <lab-id>.web-security-academy.net
Cookie: session=...
The response came back 401 Unauthorized.
Step 5 — Inspect the Response Body
Reading the response more carefully, I noticed a parameter: admin=false.
Step 6 — Modify the Parameter
I changed admin=false to admin=true in the request and resent it. This time the response was 200 OK.
Step 7 — Find the Admin Action Endpoint
Further down in the response, an action path was visible: /admin/delete?username=carlos.

Step 8 — Trigger the Action
I changed the Repeater request’s path from /admin to /admin/delete?username=carlos and sent it.

Lab solved.
Why This Works
The server is trusting a value that the client controls (admin=true/admin=false) as its source of truth for authorization, instead of checking the actual authenticated user's role against a server-side record.
Real-World Lesson
Never trust a role or permission flag that arrives from the client — whether it’s in a cookie, a hidden parameter, or a request body. Role checks must be resolved server-side against data the user cannot edit.
Lab 4 — User Role Can Be Modified in User Profile
Vulnerability / Concept
The application stores the user’s role (roleid) as an editable field that gets sent along with an unrelated action — updating your email address — and the server trusts whatever roleid it receives back.

Lab Objective
Escalate to roleid = 2 to unlock access to /admin, then delete carlos.
What I Looked For
The description explicitly stated that /admin requires roleid = 2. After logging in, I confirmed my account's current roleid was 1, so I went looking for any place in the app where that value gets sent to the server.
Step-by-Step Walkthrough
Step 1 — Read the Description and Log In
The description confirmed the roleid = 2 requirement for admin access. I logged in with the given credentials.
Step 2 — Check Current Role
I confirmed my account currently had roleid: 1.
Step 3 — Find the Relevant Function
I located the “update email” feature and captured its request/response in Burp Suite while performing a normal email update.
Step 4 — Inspect the Request Body
The request body included the roleid field alongside the email field:
PUT /my-account/change-email HTTP/2
Host: <lab-id>.web-security-academy.net
Cookie: session=...
Content-Type: application/json
{"email":"[email protected]",
"roleid": 1
}Step 5 — Modify the Role
I sent this request to Repeater and changed roleid from 1 to 2:
{"email":"[email protected]",
"roleid": 2
}
Step 6 — Confirm the Change
The response confirmed roleid: 2 had been accepted and saved.
Step 7 — Access the Admin Panel
I navigated to /admin, which was now accessible, located carlos, and deleted the account.


Lab solved.
Why This Works
The server treats the roleid field as just another user-editable profile attribute instead of a protected, privileged value. Because the endpoint doesn't validate or strip that field server-side, the client can set it to whatever it wants.
Real-World Lesson
Sensitive fields like roles, permissions, or account tiers should never be accepted directly from client input on general-purpose “update profile” endpoints. If a value determines privilege, it needs its own protected code path with proper authorization checks — not a free-text field a user can edit.
Lab 5 — URL-Based Access Control Can Be Circumvented
Vulnerability / Concept
The application blocks access to /admin using a front-end control (like a reverse proxy or load balancer rule based on the URL path) rather than a genuine server-side authorization check. A supported-but-overlooked header lets you bypass that front-end restriction entirely.

Lab Objective
Use the X-Original-URL header to reach the admin functionality and delete carlos.
What I Looked For
The description mentioned that /admin was blocked at the front-end. That's a strong hint that the back-end application itself might still process admin requests if you could reach it directly — and some back-ends support alternate headers (like X-Original-URL) for routing, which front-end filters often forget to inspect.
Step-by-Step Walkthrough
Step 1 — Read the Description
It confirmed /admin was blocked at the front-end system, not the application itself.

Step 2 — Review Requests in Burp
I looked through the application’s requests for anything related to deleting a user.
Step 3 — Add the Bypass Header
I took a normal request and added an X-Original-URL header pointing to the admin path:
GET / HTTP/2
Host: <lab-id>.web-security-academy.net
X-Original-URL: /admin
Cookie: session=...
The response confirmed successful admin access.
Step 4 — Trigger the Delete Action
Using the same technique, I sent the delete request:
GET /?username=carlos HTTP/2
Host: <lab-id>.web-security-academy.net
X-Original-Url: /admin/delete
Cookie: session=...


Lab solved — the user was deleted.
Why This Works
The front-end control only inspects the literal URL path in the request line, but the back-end application honors the X-Original-URL header as the "real" path for routing purposes. Since the front-end never checks that header, the restriction is completely bypassed once the request reaches the back-end.
Real-World Lesson
Never rely on a front-end proxy, WAF, or gateway as your only line of defense for sensitive paths. If the back-end application itself doesn’t enforce authorization, any header, alias, or routing quirk that reaches it directly can bypass the “protection” entirely.
Lab 6 — Method-Based Access Control Can Be Circumvented
Vulnerability / Concept
An admin action is protected for one HTTP method (e.g., POST) but not for another (e.g., GET), letting an unauthorized user perform the same action just by changing the request method.

Lab Objective
Use a normal user’s session to upgrade carlos to admin, by switching the HTTP method the request uses.
What I Looked For
Two sets of credentials were provided — one admin, one normal user. I compared how the admin account’s “upgrade user” action behaved versus what happened when the same request was replayed with a lower-privileged session.
Step-by-Step Walkthrough
Step 1 — Log In as Both Users
I logged into the admin account and the normal user account (in separate sessions), and sent both accounts’ requests to Burp Repeater.

Step 2 — Capture the Admin Action
As the admin, I upgraded carlos to admin and captured that request in Burp's HTTP history, then sent it to Repeater.

Step 3 — Swap the Session
I replaced the session cookie in the admin’s “upgrade” request with the normal user’s session cookie, then sent the request.
Result: 401 Unauthorized — the POST request was correctly blocked for a non-admin session.

Step 4 — Change the HTTP Method
I changed the request method.
Note: the original notes mention trying an invalid method first (“POSTX”), which returned a “Missing parameter ‘username’” error rather than an authorization error — Note: the original notes do not specify the exact reasoning here, but this behavior suggests the endpoint’s authorization check is tied specifically to the POST method, while other methods skip that check but still expect the same parameters.

Step 5 — Supply the Missing Parameter
I added username=wiener to the request body.
Step 6 — Switch to GET
Using Burp’s “Change request method” feature (right-click → Change Request Method), I converted the request to GET, which moved the parameters into the query string.
Step 7 — Send the Request
The response came back as 302 — a redirect indicating success.

Step 8 — Confirm in Browser
I checked the account in the browser and confirmed the normal user had been upgraded.

Lab solved.
Why This Works
The developer added an authorization check only for the POST version of this endpoint, likely assuming users would only ever reach it that way. The same underlying action was still reachable via GET, and that code path never checked the user's role at all.
Real-World Lesson
Access control checks must be enforced at the action level, not the method level. If an endpoint performs a privileged operation, every HTTP method capable of triggering that operation needs the same authorization check — not just the one the front-end normally uses.
Lab 7 — User ID Controlled by Request Parameter
Vulnerability / Concept
A user’s own data is fetched using a simple id parameter in the URL, and the server doesn't verify that the requested id actually belongs to the logged-in user. This is a classic Insecure Direct Object Reference (IDOR).

Lab Objective
View carlos's account information by manipulating the id parameter.
What I Looked For
After logging in, I looked at the URL structure of the account page.
Step-by-Step Walkthrough
Step 1 — Log In
I logged in with the provided credentials.
Step 2 — Note the URL Pattern
The account page URL looked like this:
https://<lab-id>.web-security-academy.net/my-account?id=wiener

Step 3 — Modify the Parameter
I changed id=wiener to id=carlos:
https://<lab-id>.web-security-academy.net/my-account?id=carlos

Step 4 — Confirm the Impact
The page loaded carlos's account details, including the API key needed to solve the lab.

Step 5 — Submit the Solution
I copied the API key and submitted it.

Lab solved.
Why This Works
The server uses the id value straight from the URL to decide whose data to return, without checking whether that id matches the currently authenticated session.
Real-World Lesson
Never resolve “whose data is this?” using a value the client fully controls. The server should derive the current user’s identity from their session, and any request for another user’s data should require an explicit, checked permission — not just a matching ID in the URL.
Lab 8 — User ID Controlled by Request Parameter, with Unpredictable User IDs
Vulnerability / Concept
Same IDOR pattern as Lab 7, but this time IDs are GUIDs instead of usernames — meant to prevent guessing. The flaw is that the GUID still leaks elsewhere in the application.

Lab Objective
Find carlos's GUID and use it to access his account.
What I Looked For
Since GUIDs can’t realistically be guessed, I needed to find a place in the app where carlos's GUID was exposed some other way — most likely somewhere he interacts publicly, like a blog.
Step-by-Step Walkthrough
Step 1 — Read the Description
It confirmed that user IDs are GUIDs, not predictable usernames.

Step 2 — Log In and Check the URL
After logging in, the account URL looked like:
https://<lab-id>.web-security-academy.net/my-account?id=ff4f6815-c480-4762-b1ca-06e73916a0ff
This confirmed ff4f6815-c480-4762-b1ca-06e73916a0ff as wiener's GUID.

Step 3 — Check Burp History
I reviewed all captured requests in Burp’s HTTP history looking for any reference to carlos's GUID — nothing turned up there.
Step 4 — Check the Blog
Back on the site, I browsed the blog section to see if carlos had posted anything.
Step 5 — Open Carlos’s Post
I found a post authored by carlos and clicked on his username/profile link from the post:
https://<lab-id>.web-security-academy.net/post?postId=3

Step 6 — Extract His GUID
Clicking through revealed carlos's GUID in the resulting URL.

Step 7 — Swap the GUID
I replaced wiener’s GUID with carlos’s GUID in the my-account URL and loaded it.

Step 8 — Retrieve the Key
Carlos’s account loaded, exposing the API key needed for the solution.

Step 9 — Submit the Solution
I copied the key and submitted it.

Lab solved.
Why This Works
Using unpredictable identifiers (GUIDs) makes blind guessing impractical, but it doesn’t fix the underlying problem: the server still doesn’t check whether the requested ID belongs to the current user. Since the GUID was exposed elsewhere in the app (the blog), the “unpredictability” defense was irrelevant.
Real-World Lesson
Obscurity (hard-to-guess IDs) is not a substitute for authorization checks. If IDs for other objects are discoverable anywhere in your application — even indirectly — an attacker will eventually find them. The only real fix is validating server-side that the requester is entitled to the specific object being requested.
Lab 9 — User ID Controlled by Request Parameter with Data Leakage in Redirect
Vulnerability / Concept
Same IDOR root cause, but here, even though the app tries to redirect away from the unauthorized data, the sensitive information is already present in the body of the redirect response itself.

Lab Objective
Access carlos's data via a leaked value inside a redirect response.
What I Looked For
The description hinted that information was leaking somewhere in a response body, so I paid close attention not just to status codes, but to the full response content — even on redirects.
Step-by-Step Walkthrough
Step 1 — Log In and Explore
I logged in with the provided credentials and reviewed every function, checking requests in Burp Suite along the way — including the login request and the email update request.

Step 2 — Find the ID Parameter
I located the familiar pattern:
/my-account?id=wiener

Step 3 — Swap the ID
I changed id=wiener to id=carlos and sent the request.

Step 4 — Read the Full Response
The server responded with a 302 redirect (implying access was denied and the app was bouncing the request away) — but I read the entire response body anyway instead of just noting the status code.

Step 5 — Spot the Leak
The response body — despite the redirect — still contained carlos’s data, including the key needed to solve the lab.
Step 6 — Submit the Solution
I copied the leaked key and submitted it.

Lab solved.

Why This Works
The developer correctly blocked the request from displaying the unauthorized page (hence the redirect), but the server had already rendered the sensitive data into the response body before issuing that redirect. Blocking the redirect target isn’t the same as never generating the sensitive content in the first place.
Real-World Lesson
Authorization checks need to happen before any sensitive data is generated or attached to a response — not after, with a redirect tacked on as an afterthought. Always inspect full response bodies during testing, even on 3xx redirects; the interesting part is sometimes right there below the status line.
Lab 10 — User ID Controlled by Request Parameter with Password Disclosure
Vulnerability / Concept
Another IDOR, but swapping the ID to a privileged account (administrator) not only exposes that account's profile page — it also exposes the account's actual password in plaintext on the page.

Lab Objective
View the administrator’s password via the IDOR and use it to log in and delete carlos.
What I Looked For
After logging in as a normal user, I looked for the same id parameter pattern seen in earlier labs, and considered what would happen if I pointed it at a privileged account instead of another regular user.
Step-by-Step Walkthrough
Step 1 — Log In and Explore
I logged in with the given credentials and checked all functions, including the blog and the update-email feature.

Step 2 — Note the URL Pattern
The account page followed the same structure:
https://<lab-id>.web-security-academy.net/my-account?id=wiener

Step 3 — Target the Administrator Account
I changed id=wiener to id=administrator and loaded the page directly.
Step 4 — Access Granted
The administrator’s account page loaded without any additional check.

Step 5 — Locate the Password
The password field on the page contained the administrator’s actual password. I confirmed this two ways:
- Inspecting the raw HTTP response in Burp Suite.
- Right-clicking the page in the browser and using “View Page Source.”

Step 6 — Log In as Administrator
I logged out, then logged back in using administrator and the disclosed password.
Step 7 — Use Admin Access
With the admin panel now accessible, I located carlos and deleted the account.

Lab solved.

Why This Works
This combines two failures: the IDOR itself (no check on whether the requested id matches the session), plus a design flaw where a sensitive credential (the password) is rendered directly onto a profile page instead of being masked or omitted entirely.
Real-World Lesson
Never render sensitive credentials — passwords, tokens, secrets — onto any page, even a page the “right” user is supposed to see. Combine that with a real authorization check on the underlying id parameter, and this class of bug disappears entirely.
Lab 11 — Insecure Direct Object References
Vulnerability / Concept
A support chat feature stores conversation transcripts as individual files on the server, and those files are accessible by direct, predictable filename — with no check on who’s allowed to view which transcript.

Lab Objective
Find another user’s (carlos's) password by browsing chat transcript files.
What I Looked For
Once I noticed the chat feature offered a “download transcript” option, I paid attention to what that download request actually looked like — specifically, the filename.
Step-by-Step Walkthrough
Step 1 — Log In and Explore
I logged in with the given credentials and reviewed the site’s functions — one of which was a live chat feature.

Step 2 — Use the Chat Feature
I opened a chat conversation and sent a few messages, noting that transcripts appeared to be saved server-side.

Step 3 — Download the Transcript
I used the “view transcript” / download option, which triggered a file download such as 1.txt or 5.txt.

Step 4 — Inspect the Request in Burp
I captured the download request in Burp Suite and examined the filename parameter.

Step 5 — Iterate Through Filenames
I changed the filename — for example, from 1.txt to 2.txt — and resent the request, checking each response.

Step 6 — Find Carlos’s Password
By working through the numbered transcript files, I eventually found one belonging to carlos that contained his password in the conversation text.

Step 7 — Log In as Carlos
I used the disclosed password to log into carlos's account.

Lab solved.
Why This Works
Transcript files are referenced by simple sequential numbers with no ownership check — anyone who can guess or iterate through the numbering scheme can read any user’s transcript, including any sensitive information a user might type into a support chat.
Real-World Lesson
Any object referenced by a predictable identifier (sequential IDs, simple filenames) needs an ownership check before it’s served — never assume a filename being “just a download link” makes it safe to expose without authorization. Also: users should be warned never to type credentials into support chats.
Lab 12 — Multi-Step Process with No Access Control on One Step
Vulnerability / Concept
A privileged multi-step action (promoting a user to admin) correctly checks authorization on the first step, but a later step in the same flow — the actual confirmation step — doesn’t repeat that check.

Lab Objective
Trick the multi-step “upgrade user” process into upgrading the normal user account, by exploiting the missing check on the confirmation step.
What I Looked For
The description made clear that two accounts were provided, and the goal was to become admin using only the lower-privileged account. That meant I needed to understand the entire multi-step flow the admin normally uses to promote someone, not just the first request.
Step-by-Step Walkthrough
Step 1 — Log In as Both Users
I logged into the normal user account in one browser session, then opened a private/incognito window and logged into the admin account separately.


Step 2 — Walk Through the Admin’s Upgrade Flow
As the admin, I initiated the process to upgrade carlos to admin, and watched the full flow in Burp's HTTP history.

Step 3 — Trigger the Confirmation Step
The browser showed a Yes/No confirmation prompt. I clicked Yes, which fired off a second request — the real action:
POST /admin-roles HTTP/2
Host: <lab-id>.web-security-academy.net
Cookie: session=...
action=upgrade&confirmed=true&username=carlos

Step 4 — Send Both Requests to Repeater
I sent this confirmation request to Repeater. I also sent the normal user’s own login request to Repeater, so I could copy its session cookie.
Step 5 — Swap the Session
In the confirmation request (the one that upgrades a user), I replaced the admin’s session cookie with the normal user’s session cookie.

Step 6 — Change the Target Username
I changed username=carlos to username=wiener (the normal user), keeping action=upgrade&confirmed=true.

Step 7 — Send the Request
The request succeeded — even using the low-privileged session — because this confirmation step never re-checked whether the session belonged to an admin.

Lab solved — wiener was upgraded to admin.
Why This Works
The developer put the authorization check on the first step of the flow (the initial “upgrade this user” request, which shows the confirmation prompt) but forgot to repeat that same check on the step that actually performs the change. Anyone who can reach that final confirmation request directly — bypassing the earlier UI step — inherits its lack of a check.
Real-World Lesson
In any multi-step process, authorization must be enforced on every step — not just the first one a user normally sees. Attackers don’t have to follow your intended flow in order; they can jump straight to whichever request in the sequence actually performs the sensitive action.
Lab 13 — Referer-Based Access Control
Vulnerability / Concept
The application decides whether to allow an admin action based on the Referer header — trusting that if a request "came from" the admin panel page, it must be legitimate. The Referer header is fully controlled by the client and easy to forge.

Lab Objective
Promote the normal user account to admin by exploiting the flawed Referer check.
What I Looked For
With two accounts provided again, I compared what a legitimate admin-panel-driven upgrade request looked like versus a request sent from anywhere else, focusing specifically on header differences.
Step-by-Step Walkthrough
Step 1 — Log In as Both Users
I logged into the admin account, then opened a private window and logged into the normal (wiener) account separately, sending wiener’s request to Repeater to preserve its session.


Step 2 — Perform a Legitimate Upgrade as Admin
As the admin, I upgraded carlos to admin through the actual admin panel UI, and captured the resulting request in Burp's HTTP history.


Step 3 — Send It to Repeater
The captured request looked like this:
GET /admin-roles?username=carlos&action=upgrade HTTP/2
Host: <lab-id>.web-security-academy.net
Referer: https://<lab-id>.web-security-academy.net/admin
Cookie: session=...
Step 4 — Change the Target Username
I changed username=carlos to username=wiener.

Step 5 — Swap the Session
I replaced the admin’s session cookie with wiener’s session cookie (captured earlier from wiener’s own request in Repeater).

Step 6 — Send the Request
Because the request still carried a Referer header pointing to /admin — the header the server was actually trusting — the action succeeded even though the session belonged to a non-admin user.

Lab solved — wiener was upgraded to admin.
Why This Works
Referer is just another client-supplied HTTP header — it's trivial to set to any value in Repeater. Using it as an authorization signal is functionally the same as having no authorization check at all, since nothing stops a client from sending whatever Referer value it wants.
Real-World Lesson
Never use client-controlled headers (Referer, Origin, X-Forwarded-*, or similar) as your basis for an authorization decision. The only trustworthy signal is something the server verifies itself — a validated session tied to a role that's checked server-side, not a header the client can trivially rewrite in a proxy tool.
Final Thoughts
Going through these 13 labs back-to-back really drives home a pattern: broken access control almost never requires exotic techniques. It requires reading the request carefully, questioning every value the client sends, and checking whether the server actually re-verifies permission on every single action — not just the first step, not just the “normal” HTTP method, not just the path the front-end expects you to take.
If you’re testing your own applications, a simple habit will catch a surprising number of these issues: for every sensitive action, ask “what happens if a lower-privileged user sends this exact request?” — then actually go test it with a tool like Burp Suite, instead of assuming the UI’s restrictions are the whole story.
If this walkthrough helped, I’ll be covering more Web Security Academy categories in future write-ups — feel free to follow along.
👤 About the Author
Nitish Mukhiya is a security researcher and bug bounty hunter focused on web application security, currently sharpening his skills through PortSwigger’s Web Security Academy.
Connect:
- 🐦 Twitter/X — @NITISHMUKHIYAJ
- 💼 LinkedIn — linkedin.com/in/nitishmukhiya
- 💻 GitHub — github.com/nitishmukhiyaji
- ✍️ Medium — medium.com/@nitishmukhiya
- 🌐 Portfolio — https://www.nitishmukhiya.com/
Broken Access Control: A Practical Walkthrough of 13 PortSwigger Labs was originally published in System Weakness on Medium, where people are continuing the conversation by highlighting and responding to this story.