Skip to content

Broken Access Control

Bron: PortSwigger Web Security Academy
Auteur: Johan Beysen


1. Wat is Broken Access Control?

Broken Access Control treedt op wanneer een applicatie gebruikers toelaat acties uit te voeren of data te bekijken waarvoor ze geen rechten hebben. Het is consistent de meest voorkomende kwetsbaarheid in de OWASP Top 10.

Twee hoofdtypes:

Type Omschrijving
Verticale BAC Lager-geprivilegieerde gebruiker krijgt toegang tot admin/hogere functies
Horizontale BAC Gebruiker krijgt toegang tot data van een andere gebruiker met gelijke rechten

Impact:

  • Ongeautoriseerde toegang tot admin functionaliteit
  • Inzage in gevoelige data van andere gebruikers
  • Account takeover
  • Data manipulatie of verwijdering

2. Detectie

Test Wat je zoekt
robots.txt, sitemap.xml Paden van admin of gevoelige endpoints
JS broncode (View Source) Hardcoded paden of role-checks
Cookies en headers Client-stuurbare role/privilege-waarden
ID-parameters swappen Horizontale toegang tot andere accounts
HTTP methode wisselen Check enkel op POST maar niet op GET
Stap 2 van multi-step direct sturen Ontbrekende check op bevestigingsstappen

Burp Suite

Gebruik Burp Repeater om raw responses te bekijken, ook bij redirects. Burp Spider pikt hardcoded paden in JS automatisch op.


3. Admin Functionaliteit Onbeveiligd

3.1 Informatielekken via robots.txt

robots.txt instrueert zoekmachines welke paden ze moeten overslaan. Het is publiek toegankelijk.

GET /robots.txt

User-agent: *
Disallow: /administrator-panel   ← pad gelekt
curl https://target.com/robots.txt

Probeer elk Disallow-pad direct. Geen authenticatie? Dan is het raak.

Ironisch

Door een pad te "verbergen" voor zoekmachines maak je het zichtbaar voor elke aanvaller. robots.txt is geen beveiligingsmaatregel.

Kijk ook naar /sitemap.xml voor vergelijkbare lekken.

Fix:

  • Authenticatie en autorisatie afdwingen op de admin route zelf
  • Admin panel achter VPN of IP-whitelist

3.2 Paden in JS Broncode

Het admin pad staat hardcoded in client-side JavaScript — zichtbaar voor iedereen, ongeacht de UI-staat.

var isAdmin = false;
if (isAdmin) {
    var adminPanelTag = document.createElement('a');
    adminPanelTag.setAttribute('href', '/admin-7hf93k');  // ← pad gelekt
}

Zoek via View Source (Ctrl+U) of DevTools → Sources:

admin   panel   dashboard   hidden   secret   manage

Fix:

  • Nooit gevoelige paden of logica in client-side JS zetten
  • JS minification/obfuscation is geen beveiliging

4. Client-Side Enforcement Bypasses

Na inloggen zet de server een cookie die de rol bepaalt. De applicatie vertrouwt blind op die waarde.

Set-Cookie: Admin=false   ← door server gestuurd
→ Verander naar: Admin=true
→ Toegang tot admin panel verkregen

Zoek in DevTools → Application → Cookies naar:

role=   admin=   isAdmin=   userType=   level=   privilege=

Kernprobleem

Access control mag nooit gebaseerd zijn op client-stuurbare waarden. De client kan alles sturen wat hij wil — cookies, headers, body parameters.

Fix:

  • Rol/rechten uitsluitend server-side opslaan, in de sessie
  • Session ID in cookie, rechten ophalen uit database op basis van die sessie

4.2 Mass Assignment via JSON Body

De server accepteert extra JSON-velden in een request die hij niet verwacht:

// Normale request:
{"email": "user@example.com"}

// Aanvalsrequest:
{"email": "user@example.com", "roleid": 2}

// Response:
{"username": "user", "email": "...", "roleid": 2}    gelukt

De server mapt de JSON input direct naar een object zonder whitelist van toegestane velden. Dit heet mass assignment of auto-binding.

Herkennen: API responses bevatten meer velden dan je verstuurt (roleid, isAdmin, verified...). Die extra velden in de response → probeer ze ook te sturen in requests.

Fix:

  • Explicit whitelist van toegestane velden bij model binding
  • Laravel: $fillable | Rails: strong parameters | Spring: @JsonIgnore

5. Header-Based Bypasses

5.1 X-Original-URL

De front-end proxy blokkeert /admin op URL. De back-end ondersteunt de X-Original-URL header.

GET / HTTP/1.1
X-Original-URL: /admin

Front-end ziet / → geen blokkering. Back-end ziet de header → serveert /admin.

Query parameters bij acties meegeven via de normale URL:

GET /?username=target HTTP/1.1
X-Original-URL: /admin/delete

Testmethode:

  1. Probeer /admin direct → geblokkeerd
  2. GET / + X-Original-URL: /invalid → "not found" van back-end? Header wordt verwerkt
  3. X-Original-URL: /admin → toegang

Vergelijkbare headers

X-Rewrite-URL, X-Forwarded-URL

Fix:

  • X-Original-URL strippen uit inkomende externe requests op de proxy
  • Back-end access control implementeren, niet enkel op de proxy laag

5.2 Referer-Based Access Control

De server controleert of de Referer header verwijst naar een admin pagina:

GET /admin-roles?username=wiener&action=upgrade HTTP/1.1
Referer: https://vulnerable-site.com/admin   ← zelf toegevoegd
Cookie: session=<non-admin-session>

→ 200 OK, actie uitgevoerd

De Referer header is volledig client-stuurbaar.

Header Ogenschijnlijk doel Realiteit
Referer Vorige pagina aanduiden Vrij instelbaar door client
X-Original-URL URL override Vrij instelbaar door client
X-Forwarded-For Origineel IP bij proxy Vrij instelbaar door client
Cookie (role=admin) Sessie-info Vrij instelbaar door client

Fix:

  • Nooit access control baseren op Referer, Origin of andere client-stuurbare headers

6. HTTP Method Bypass

De access control check zit enkel op POST-requests:

POST /admin/promote       → 403   (check aanwezig)
GET  /admin/promote?username=wiener  → 200 OK  (geen check)
# Via ffuf alle methodes testen
ffuf -u https://target.com/admin/promote -X FUZZ -w methods.txt

Burp tip

"Change request method" in Burp Repeater verplaatst POST body-parameters automatisch naar de query string bij conversie naar GET.

Fix:

  • Access control binden aan de route, niet aan de methode
  • Onbekende methodes → 405 Method Not Allowed

7. Multi-Step Process Bypass

Access control enkel op stap 1, niet op de bevestigingsstap:

Stap 1: POST /admin/promote          → 403   (check aanwezig)
Stap 2: POST /admin/promote/confirm  → 200 OK (geen check)

Kernfout

Developers gaan ervan uit dat stap 2 enkel bereikbaar is via stap 1. Maar HTTP heeft geen "state" — elke request is onafhankelijk.

Testmethode:

  1. Log in als admin, voer de volledige flow uit, intercept alle requests in Burp
  2. Log in als gewone user, kopieer je session cookie naar de stap-2-request
  3. Stuur stap 2 direct, zonder stap 1 te doen

Fix:

  • Access control op elke stap van een multi-step flow
  • Server-side state bijhouden: controleer of stap 1 succesvol doorlopen is

8. IDOR (Insecure Direct Object Reference)

De applicatie gebruikt een gebruiker-gecontroleerde waarde om direct een object op te halen, zonder te controleren of die gebruiker daar toegang toe heeft.

8.1 Voorspelbaar ID

GET /my-account?id=wiener   ← jouw account
GET /my-account?id=carlos   ← carlos zijn account

Testen:

  • Maak twee accounts aan
  • Log in als account A, pas id-parameter aan naar ID van account B
  • Burp Intruder: brute force numerieke IDs

Fix:

  • Server-side controleren of de ingelogde gebruiker eigenaar is van het gevraagde object
  • Indirect references (mapping table) in plaats van directe DB-IDs

8.2 GUID / Onvoorspelbaar ID

De applicatie gebruikt GUIDs maar die lekken via andere pagina's:

Blog post auteur-URL:  /blogs?userId=fe89a329-d98f-4a26-b...   ← GUID gelekt
Account URL:           /my-account?id=fe89a329-d98f-4a26-b...  ← zelfde GUID

Kernprincipe

Een onvoorspelbaar ID is geen access control. Security through obscurity: als het ID ergens anders lekt, is de "beveiliging" volledig nutteloos.

Zoek of user-IDs ergens in de applicatie voorkomen: blog auteurs, forum posts, reacties, e-mail headers.

Fix:

  • GUID ≠ access control: autorisatiecheck blijft verplicht
  • Nooit user-specifieke IDs lekken in publiek toegankelijke content

8.3 Data Leakage in Redirect

De server stuurt een 302 redirect als je een ID opvraagt dat niet van jou is. Maar de response body bevat de gevoelige data al — vóór de redirect uitgevoerd wordt.

HTTP/1.1 302 Found
Location: /home

<body>
API key: abc123xyz...   ← browser negeert dit, Burp wel
</body>
Tool Gedrag bij 302 redirect
Browser Negeert body, volgt redirect
Burp Repeater Toont volledige raw response inclusief body

Fix:

  • Response body leegmaken vóór de redirect verstuurd wordt
  • Data pas ophalen na succesvolle autorisatiecheck

8.4 Bestanden met Voorspelbare Naam

Bestanden opgeslagen met een incrementeel of voorspelbaar nummer:

GET /download-transcript/3.txt   ← jouw bestand
GET /download-transcript/1.txt   ← bestand van iemand anders
ffuf -u https://target.com/download-transcript/FUZZ.txt -w numbers.txt

Fix:

  • Bestanden opslaan met niet-voorspelbare namen (UUID)
  • Server-side controleren of de gebruiker eigenaar is van het bestand

8.5 Password Disclosure via IDOR

De profielpagina geeft het wachtwoord terug in de response:

GET /my-account?id=wiener          ← eigen wachtwoord zichtbaar
GET /my-account?id=administrator   ← admin wachtwoord zichtbaar

Impact

Naast data-inzage leidt dit direct tot account takeover. Impact: maximaal.

Fix:

  • Wachtwoorden nooit in responses sturen, ook niet masked
  • Autorisatiecheck: controleer of de sessiegebruiker = eigenaar van het gevraagde account
  • Wachtwoorden alleen gehasht opslaan

9. Patronen & Gouden Regels

De Drie Rode Draden

1. Beveiliging gebaseerd op client-stuurbare waarden

Cookies, headers, query parameters, JSON body — de client controleert dit allemaal. Vertrouw het nooit voor access control beslissingen.

2. Access control in één laag, omzeilbaar via een andere vector

Front-end blokkeert? Probeer de back-end direct. Check enkel op POST? Probeer GET. Enkel stap 1 beveiligd? Stuur stap 2 rechtstreeks.

3. Gevoelige data lekt via onverwachte kanalen

Redirect bodies, JS broncode, robots.txt, blog auteur-URLs, API responses — data lekt overal als developers er niet actief op letten.

Samenvatting per Techniek

Techniek Kernles
robots.txt leak robots.txt is geen beveiliging
JS broncode leak Client-side JS verbergt niets
Cookie role param Nooit rechten in client-stuurbare cookies
Mass assignment Whitelist je model binding
X-Original-URL Headers zijn client-stuurbaar
Method bypass Check de route, niet de methode
Multi-step bypass Elke stap afzonderlijk beveiligen
Referer check Referer is client-stuurbaar
IDOR simpel ID Altijd autorisatiecheck op object
IDOR GUID GUID ≠ access control
IDOR redirect leak Body leegmaken voor redirect
IDOR bestanden Bestanden niet direct ophaalbaar met voorspelbare naam
Password via IDOR Wachtwoorden niet in responses

Gouden regels

  • Vertrouw nooit client-gestuurde input voor autorisatiebeslissingen
  • Implementeer access control op de back-end, op elke route, elke methode, elke stap
  • Controleer altijd: is de ingelogde gebruiker eigenaar van het gevraagde object?
  • Gevoelige data pas ophalen na succesvolle autorisatiecheck