ankur@sec:~$

$ cat owasp/a01.md

A01:2025

Broken Access Control

The app checks who you are, but forgets to check what you are allowed to touch.

## 1 · why it happens

Authentication answers "who are you?". Access control answers "is this yours?". Developers usually get the first one right because the framework does it. The second one is business logic - and it has to be written for every endpoint, by hand.

The classic case is IDOR: the API takes an ID from the URL and returns that record without checking the owner. In 2025 OWASP also moved SSRF here - the server fetching any URL the user gives it is another way of reaching things you should not.

# JavaScript

✗ vulnerable
// GET /api/invoices/1002
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);
  res.json(invoice); // any logged-in user can read any invoice
});
✓ fixed
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const invoice = await db.invoices.findOne({
    id: req.params.id,
    ownerId: req.user.id, // scope every query to the current user
  });
  if (!invoice) return res.sendStatus(404);
  res.json(invoice);
});

# C#

✗ vulnerable
[Authorize]
[HttpGet("api/invoices/{id}")]
public async Task<IActionResult> Get(int id)
{
    var invoice = await _db.Invoices.FindAsync(id);
    return Ok(invoice); // no ownership check
}
✓ fixed
[Authorize]
[HttpGet("api/invoices/{id}")]
public async Task<IActionResult> Get(int id)
{
    var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
    var invoice = await _db.Invoices
        .SingleOrDefaultAsync(i => i.Id == id && i.OwnerId == userId);
    return invoice is null ? NotFound() : Ok(invoice);
}

## 2 · how attackers find it from outside

public sourcewhat it tells them
Wayback MachineOld API routes like /api/v1/users/123 that may still work without the checks added in v2.
JavaScript bundles & source mapsEvery route the frontend calls - including admin routes that are only "hidden" in the UI.
Public API docs / SwaggerWhich endpoints take IDs, so the attacker knows exactly what to increment.

→ all OSINT sources · run them on yourself

## 3 · how it's abused

⚠ Recon your own assets, or targets you have written permission to test. Passive OSINT reads public data, but using it to target someone else's systems can still be illegal or against their terms. Examples on this site use example.com placeholders.

GET /api/invoices/1002

IDOR: the attacker owns invoice 1001 and simply asks for the next number.

../../../etc/passwd

Path traversal: climbs out of the upload/download folder to read system files.

..%2f..%2f..%2fetc%2fpasswd

Same thing URL-encoded, to slip past a filter that only blocks the literal "../".

http://169.254.169.254/latest/meta-data/

SSRF: makes your server call the cloud metadata service and leak its own credentials.

Origin: https://evil.example

CORS: if the response reflects this origin with credentials allowed, any site can read your users' data.

Payload examples adapted from PayloadsAllTheThings (MIT, © Swissky). Full lists:Insecure Direct Object References · Directory Traversal · Server Side Request Forgery · CORS Misconfiguration · Cross-Site Request Forgery

## 4 · fix

## 5 · detect

test it

ZAP

Open-source web app scanner and proxy. The baseline scan is safe for CI.

authorized targets only
fix at source

Bearer

Static analysis that follows sensitive data through your code.

osint / reconpassive

Wayback Machine (CDX API)

Every URL of your site the Internet Archive has seen - including the ones you forgot.

Official description: OWASP A01:2025 Broken Access Control