Broken Access Control in Practice — When Changing One Number in a URL Exposes Every Customer
IDOR is one of the vulnerability classes automated scanners struggle with most. The real-world pattern, and how to fix it at the code level.
Change /orders/1042 to /orders/1043 in the URL, and if someone else's order shows up — that's not a coincidence. It's a broken access control (authorization) vulnerability. The industry calls it IDOR (Insecure Direct Object Reference), and it's one of the hardest vulnerability classes for automated scanners to catch.
Authentication and authorization are different things
Whether you're logged in (authentication) and whether the logged-in person is entitled to see that specific piece of data (authorization) are two completely separate questions. Most services build login carefully but forget to ask "is this user actually the owner of this resource?"
How it actually gets exploited
GET /api/orders/1042 → your own order (fine) GET /api/orders/1043 → someone else's order, shown as-is (vulnerable)
This happens when the server code looks roughly like this:
// Vulnerable: looks up by id only, never checks ownership
app.get('/api/orders/:id', async (req, res) => {
const order = await db.order.findUnique({ where: { id: req.params.id } });
res.json(order);
});It only checks that you're logged in — it never checks whether this order belongs to the logged-in user. Just incrementing a numeric ID lets someone walk through every customer's orders.
Why scanners tend to miss it
A typical automated scanner is good at patterns like "does this API return 200," or "is this endpoint SQL-injectable." IDOR is harder, because the response legitimately returns 200 with real, valid data — you need to understand the business context of "whose data is this" to know something's wrong. That takes analysis a level deeper than simple pattern matching.
How to fix it
// Fixed: the lookup condition always includes the owner (userId)
app.get('/api/orders/:id', async (req, res) => {
const order = await db.order.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.status(404).end();
res.json(order);
});There's one principle: always include the current logged-in user in the lookup condition. Treat any query that looks up a resource by ID alone as suspect.
How to test for it
The most reliable method: create two accounts, then, while logged in as account A, directly request a resource ID belonging to account B. If you get back a 200 with real data, you have a problem. A 404 or 403 is the correct behavior.
IDOR doesn't require fancy hacking skills. It's discovered and exploited just by changing one number in the browser's address bar. That's exactly what makes it so dangerous.
WhiteHat Code's deep scan analyzes the business context of your code alongside its logic to catch this kind of missing-authorization pattern. Check yours now with a free scan.