The Exploit HQ

Web Application Security for Beginners · lesson 1 · 2026-08-23 · updated 2026-10-12

Broken Access Control & IDOR — Web Application Security for Beginners

Before you start

Welcome to The Exploit HQ, and to a brand new beginner series on web application vulnerabilities. This is part one, and you need zero experience to follow along.

Today we learn the flaw that currently sits at number one on the industry’s list of web weaknesses, the O-WASP Top Ten. It is called broken access control, and the idea is simple enough for anyone to follow.

First, the words. Access control is just the set of rules that decides who is allowed to see or do what.

What is access control?

Your password is access control. The lock on a changing room is access control. It keeps your things yours. When it is broken, those rules do not really stop you, and you can reach things that were never meant to be yours.

Here is the exact trick we will see today, drawn out.

You log in as yourself. Then you ask the app for some data using a number, like basket number one. If the app forgets to check that the basket is really yours, it just hands it over.

So you change the one to a two, and you get user two’s basket. Change it to a three, and you get user three’s. That is the whole flaw.

Now picture that number pointing at something other than a shopping basket.

Why should you care?

A bank statement. A medical record. A private message. The same simple bug, but now a serious data breach. That is why this sits at the very top of the O-WASP Top Ten, and why real companies still get hit by it.

We only need the terminal and three simple ideas.

The tools we’ll use

curl is a small program that sends a web request from the terminal. Think of it as a browser with no window. A token is a long secret code the app gives you when you log in, to prove who you are. And jq is a tiny helper that takes the app’s reply and prints it neatly, so a human can read it.

Let’s break down our very first command before we run it, piece by piece.

Step 1 — log in and save our token

curl -s .../rest/user/login -H "Content-Type: application/json" -d '{"email":"…","password":"…"}' | jq -r .authentication.token > token.txt
Part What it does
curl -s send a web request quietly, with no progress bar
.../rest/user/login the app’s login door — where we send our details
-H "Content-Type: json" tell the app we’re sending data as JSON, a simple text format
-d '{email,password}' the data we send: our email and our password
| jq -r .…token from the app’s reply, pull out just the token
> token.txt save that token into a file so we can reuse it later

In plain terms: we hand the app our email and password, it hands us back a token, and we tuck that token into a file. Now let’s actually open the terminal and type it together.

Type it with me, slowly.

curl -s http://127.0.0.1:3000/rest/user/login -H "Content-Type: application/json" -d '{"email":"demo@ehq.local","password":"Exploit-HQ-123"}' | jq -r .authentication.token > token.txt

Notice nothing printed, and that is exactly right, because we sent the token straight into our file, token dot txt. Think of that file as a wristband from the door. Show the wristband, and the app lets us back in without logging in again.

Now the important command. Let’s break this one down too.

Step 2 — read a basket (then change the number)

curl -s -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:3000/rest/basket/1 | jq '{basket, owner_user, items}'
Part What it does
curl -s send a web request quietly
-H "Authorization: Bearer …" attach our login token, so the app knows we’re logged in
$(cat token.txt) read our saved token back out of the file
/rest/basket/1 ask for basket number 1 — THIS is the number we’ll change
| jq '{…}' tidy the reply and show the basket, its owner, and the items

So we prove we are logged in with our token, we ask for basket number one, and jq tidies the answer. Keep your eye on that last number.

Here we go.

curl -s -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:3000/rest/basket/1 | jq '{basket: .data.id, owner_user: .data.UserId, items: [.data.Products[].name]}'

Now look closely at the line that says owner user. This basket belongs to user number one. That is not us. We are logged in on our own account, yet the app cheerfully handed us a stranger’s shopping cart, with their apple, orange, and eggfruit juice.

Now watch what happens when we change just one thing. We take the very same command and turn the one into a two.

curl -s -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:3000/rest/basket/2 | jq '{basket: .data.id, owner_user: .data.UserId, items: [.data.Products[].name]}'

A completely different person, user two, and their raspberry juice. Let’s do it one more time, with a three.

curl -s -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:3000/rest/basket/3 | jq '{basket: .data.id, owner_user: .data.UserId, items: [.data.Products[].name]}'

User three. Every single time we change that one number, the app shows us another person’s private basket. It checked that we were logged in, but it never checked that these baskets were actually ours. That, in a nutshell, is broken access control.

So how do you handle this flaw?

Find it, and fix it

To find it, you do exactly what we did. Change the I D number, and see if you get back data that should not be yours. To fix it, the server must check ownership. Not just, are you logged in, but, does this basket actually belong to you. That one extra check closes the whole hole.

One rule, always.

Before you go

Only ever test apps you own, or have clear written permission to test. Our juice shop runs only on our own machine, and it was built by O-WASP for exactly this kind of safe practice.

In part two, we will make a web app cough up files it was never meant to show, a flaw called file inclusion. Thanks for learning with The Exploit HQ, and I will see you there.