Lesson 5 · Reading · 30 min
Your first bug class
You have everything you need now. You can read a request and send one three ways, you know the browser is not a boundary, and you know your identity is a value you carry. This last lesson puts those together into your first real bug class, the one that pays the most bounties and is the best place a beginner can start: broken access control.
Authentication is not authorization
Two words get muddled constantly, and keeping them apart is most of understanding this whole area.
Authentication is who are you. Logging in, proving you hold the account. The last lesson was about this: the token that says "I am user 4012."
Authorization is are you allowed to do this. You are logged in as user 4012, fine, but may user 4012 read this invoice, delete this comment, view this admin page? Every request that touches something should ask this question, and answer it on the server, for that specific user and that specific thing.
Almost every interesting access-control bug is a failure of the second while the first works perfectly. You are properly logged in as yourself. The server knows exactly who you are. It just never checks whether you, specifically, are allowed the thing you asked for. Authentication passed; authorization was never done.
The most common shape: IDOR
Here is the pattern that will earn a beginner their first real finding more often than any other. An application refers to an object by an id, and puts that id somewhere you can see and change:
GET /orders/1001 GET /account?user_id=4012 POST /api/documents/8837/delete GET /invoices/2024-0155.pdf
The server takes the id, looks the object up, and returns it, without checking that the object belongs to you. So you change the number. /orders/1001 is yours; what is /orders/1000? If the server hands it over, and it is someone else's order, you have just read another person's data by changing a digit. That is an IDOR, an Insecure Direct Object Reference (you will also see it called BOLA, Broken Object-Level Authorization; same bug, newer name).
It is stunningly common, because the fix, "check on every request that this object belongs to this user", is easy to forget on the fiftieth endpoint, and because in the browser everything looked fine: the developer only ever tested with their own account, viewing their own orders, and it worked. They never changed the number. You will.
The object reference is not always a tidy integer. It can be a UUID, a filename, a hash, an email address, a base64 blob (which, as you now know, you decode first). A UUID is harder to guess than 1000, but if the app leaks it somewhere, in a list, a notification, another response, then it is just as usable. "Hard to guess" is not "checked", and only "checked" is a control.
The method that finds them: two accounts
Here is the harness that the entire Academy assumes, and the single most valuable habit you can build. Get two accounts. Two of everything: two users, and where you can, two of whatever the app manages, two orders, two documents, two projects.
Then:
- As account A, do the thing. Create an order, open a document, load your profile. Watch the request in your proxy or the Network tab, and note the id it used,
/orders/1001. - As account B, in a separate session, replay A's exact request, A's id and all.
/orders/1001, but sent as B. - Look at what comes back. If B gets A's order, the server never checked ownership, and you have a finding. If B gets a
403or a404, the check is there and you move on.
That is it. Most access-control testing is this loop, run against every endpoint that takes an id: do it as one user, replay it as another, see whose data comes back. The lab below gives you the "other account" pre-baked so you can feel the loop once; on a real target you set up both accounts yourself, and web-recon-methodology on this site walks through building that harness properly.
The honesty gate: what is, and is not, a finding
This matters from your very first report, because getting it wrong wastes triagers' time and burns your reputation. An access-control issue is only a finding if it reaches something that is not yours to reach:
- Another user's private data, or an action taken on their behalf. Yes.
- A privileged action you should not have, deleting anyone's comment, promoting yourself. Yes.
- Your own data, shown to you, through an id that happens to be in the URL. No. Reading
/orders/1001when 1001 is yours is the app working. - Data that is already public to everyone. No. You did not bypass anything.
The test is simple: did you cross a boundary to reach something you were not entitled to? If yes, it is real, and you should be able to say exactly whose data it was and how you reached it. If no, it is not, however clever the request looked. The skill web-access-control-test on this site has the full version of this gate; carry the short version from your first day: another user's data or a privileged action, not your own and not public.
What to take away
- Authentication is who you are; authorization is what you are allowed. Most access-control bugs are perfect authentication and absent authorization.
- IDOR / BOLA: an object referenced by an id the server reads without checking it belongs to you. Change the reference, see if you get someone else's thing.
- Ids you cannot guess are not ids that are checked. "Hard to guess" is not a control.
- Two accounts is the method: do it as A, replay as B, see whose data comes back.
- It is a finding only when it reaches another user's data or a privileged action, not your own and not public.
Now do the lab, then open the Academy
The lab is a shop called Parcel. It logs you in as a customer and shows you your own order, with the order number sitting in the URL. Another customer's order is one number away. Change it, and see whether Parcel ever checks whose order you asked for.
When you have solved it, you have done, with your own hands, the thing that most first bounties are made of. That is the end of this path. The next step is the Academy's first shelf, Access & identity, which is this same idea on harder, more realistic targets, and it will now read as familiar rather than frightening. Go.
This lesson has 1 lab to try what you just read.