Onvoldoende autorisatie op functieniveau
CWE-285CWE-862OWASP A01:2021Bijgewerkt 3 september 20265 min leestijd
Bij onvoldoende autorisatie op functieniveau bepaalt de interface wat een gebruiker te zien krijgt, terwijl de server niet controleert wat hij mag aanroepen. Een gewone gebruiker die het adres van een beheerfunctie kent, voert die gewoon uit. Verbergen in het menu is presentatie; autorisatie hoort aan serverzijde.
Een beheerdersfunctie die niet in het menu staat, is voor een gewone gebruiker onvindbaar, tot iemand de JavaScript-bundel opent waarin alle routes van de applicatie netjes op een rij staan. Vanaf dat moment is het verschil tussen een gewone gebruiker en een beheerder nog maar de vraag of de server het verzoek weigert. Daarvoor zorgen is het hele werk.
Wat is autorisatie op functieniveau?
Elke applicatie kent functies die niet voor iedereen bedoeld zijn: gebruikers beheren, rollen toekennen, exports draaien, instellingen wijzigen, gegevens verwijderen. Autorisatie op functieniveau is de controle die vaststelt of de aanvrager deze specifieke handeling mag uitvoeren.
Van onvoldoende autorisatie op functieniveau, internationaal broken function level authorization of BFLA, spreken we wanneer die controle alleen in de interface plaatsvindt. De knop wordt niet getoond, het menu-item ontbreekt, de route staat niet in de navigatie. Toch het endpoint bestaat, is bereikbaar, en voert uit wat hem wordt gevraagd zodra iemand het verzoek rechtstreeks stuurt.
De denkfout is dat de frontend een poortwachter zou zijn. Dat is hij niet: hij is een weergave. Wat een gebruiker ziet, bepaalt de frontend; wat een gebruiker kán, bepaalt uitsluitend de server. Vergelijk het met een lift waarin de knop voor de directieverdieping is afgeplakt. Zolang niemand eraan denkt het plakband weg te halen, werkt het. Maar de knop zit er nog, en hij doet het.
Hoe wordt gebrekkige autorisatie op functieniveau misbruikt?
Kwetsbaar:
// Wel ingelogd, maar geen controle op de rol
app.post('/api/beheer/gebruikers/:id/rol', vereistLogin, async (req, res) => {
await gebruikers.zetRol(req.params.id, req.body.rol);
res.json({ ok: true });
});
In de interface is dit endpoint alleen bereikbaar vanuit een beheerscherm dat gewone gebruikers niet te zien krijgen. Alleen het endpoint zelf vraagt alleen om een geldige sessie. Een aanvaller vindt het adres in de frontendbundel:
// In de JavaScript van de applicatie, zichtbaar voor iedereen
const routes = {
profiel: '/api/profiel',
beheerGebruikers: '/api/beheer/gebruikers',
wijzigRol: '/api/beheer/gebruikers/:id/rol',
};
En stuurt vervolgens met zijn eigen, gewone account:
POST /api/beheer/gebruikers/4471/rol HTTP/1.1
Cookie: sid=<eigen geldige sessie>
Content-Type: application/json
{"rol":"beheerder"}
HTTP/1.1 200 OK
Hij heeft zichzelf zojuist tot beheerder gemaakt. Er is geen wachtwoord geraden, geen injectie uitgevoerd en geen sessie gestolen; er is één verzoek verstuurd naar een endpoint dat hem netjes bediende.
Veilig:
// Autorisatie per route, expliciet vastgelegd
const vereistRol = (...rollen) => async (req, res, next) => {
const gebruiker = await gebruikers.zoek(req.gebruiker.id); // actuele rol
if (!gebruiker || !rollen.includes(gebruiker.rol)) {
await auditlog.schrijf('autorisatie geweigerd', {
gebruiker: req.gebruiker.id, route: req.originalUrl,
});
return res.sendStatus(404);
}
next();
};
app.post('/api/beheer/gebruikers/:id/rol',
vereistLogin,
vereistRol('beheerder'),
async (req, res) => {
await gebruikers.zetRol(req.params.id, req.body.rol);
res.json({ ok: true });
},
);
// Vangnet: elke route zonder expliciete rechten wordt geweigerd
app.use('/api', (req, res) => res.sendStatus(404));
De controle staat nu op de route zelf en gebruikt de actuele rol uit de database in plaats van een waarde uit de sessie. Het vangnet aan het eind is minstens zo belangrijk: een nieuw endpoint waar de ontwikkelaar de autorisatie vergeet, valt daardoor dicht in plaats van open. Dat is het verschil tussen een fout die tijdens het testen opvalt en een fout die in productie een kwetsbaarheid wordt.
Wat is de impact van gebrekkige autorisatie op functieniveau?
De ernst is hoog tot kritiek, omdat de uitkomst doorgaans neerkomt op het verhogen van de eigen rechten. Een gebruiker die zichzelf beheerder kan maken, heeft daarmee toegang tot alles wat de applicatie te bieden heeft.
Ook zonder die directe route zijn de gevolgen aanzienlijk. Beheerfuncties zijn per definitie de functies met de meeste reikwijdte: het exporteren van alle klantgegevens, het verwijderen van records, het wijzigen van instellingen die de beveiliging raken, het uitschakelen van tweefactorauthenticatie voor andere accounts. Eén onbeschermd endpoint uit die categorie is genoeg voor een omvangrijk incident.
Achteraf is het lastig vast te stellen, omdat alles er volstrekt normaal uitziet. De aanvaller gebruikt zijn eigen geldige account, het verzoek is correct gevormd en het antwoord is een 200. Zonder logging van autorisatiebeslissingen is er geen spoor waaruit blijkt dat iemand een functie heeft aangeroepen die niet voor hem bedoeld was.
Hoe spoor je gebrekkige autorisatie op functieniveau op?
Een tester brengt eerst alle endpoints in kaart, en kijkt daarbij nadrukkelijk verder dan wat de interface toont. De JavaScript-bundel van de frontend is daarbij de rijkste bron: die bevat vaak alle routes, inclusief die van beheerdersschermen. Verder worden API-documentatie, oudere versies en voorspelbare naamgeving gebruikt.
Vervolgens wordt elk gevonden endpoint aangeroepen met een account met minimale rechten. Slaagt het verzoek, dan is de bevinding rond. Daarbij wordt elke methode afzonderlijk getest, omdat de autorisatie soms wel op GET staat en niet op POST of DELETE. Ook worden de klassieke omwegen geprobeerd: een afwijkende schrijfwijze van het pad, een oudere API-versie die naar dezelfde functionaliteit leidt, of een header die een rol meestuurt die de applicatie ten onrechte vertrouwt. AssistSec voert deze test uit met accounts in elke beschikbare rol, omdat de interessante gaten meestal niet tussen anoniem en ingelogd zitten maar tussen twee niveaus van ingelogde gebruikers.
Hoe voorkom je gebrekkige autorisatie op functieniveau?
- Controleer bij elk endpoint aan serverzijde of de gebruiker de bijbehorende functie mag uitvoeren.
- Weiger standaard en sta alleen expliciet toe, zodat een vergeten controle leidt tot een geweigerd verzoek.
- Groepeer beheerfuncties onder een eigen pad met één centrale rolcontrole.
- Haal de rol bij elk verzoek op uit de database, niet uit de sessie of uit een token.
- Test elke HTTP-methode afzonderlijk; autorisatie ontbreekt vaak op één ervan.
- Beschouw het verbergen van knoppen en menu-items als presentatie, nooit als beveiliging.
- Verwijder of scherm oudere API-versies af, want die missen vaak de nieuwere controles.
- Vertrouw geen rol of rechten die in een header of parameter worden meegestuurd.
- Log geweigerde autorisatiepogingen, zodat verkenning door een ingelogde gebruiker zichtbaar wordt.
Bronnen
Veelgestelde vragen
Wat is het verschil met BOLA?
BOLA gaat over de vraag of u bij een bepaald object mag; BFLA over de vraag of u een bepaalde functie mag uitvoeren. Bij BOLA komt een klant bij de factuur van een andere klant, bij BFLA voert een gewone gebruiker een beheerdersfunctie uit. Beide moeten afzonderlijk worden getest.
Is het genoeg om knoppen te verbergen?
Nee, en dat is de kern van deze bevinding. Wat de interface toont is een presentatiekeuze; de server voert uit wat hem wordt gevraagd. Iedereen kan een verzoek rechtstreeks versturen. De enige plek waar autorisatie telt, is de server.
Waarom worden endpoints gevonden die nergens staan?
Uit de JavaScript-bundel van de frontend, die vaak alle routes bevat inclusief de beheerdersfuncties. Verder uit API-documentatie, uit voorspelbare naamgeving en uit oudere versies van de API. Onbekendheid is geen bescherming.
Hoe dwing ik dit structureel af?
Door de controle standaard te laten weigeren en alleen expliciet toe te staan. Een centrale laag die elke route zonder vastgelegde rechten blokkeert, maakt van een vergeten controle een storing tijdens het testen in plaats van een kwetsbaarheid in productie.
Verwante artikelen
- KwetsbaarhedenCWE-1059A05:2021Verouderde API-versies blijven bereikbaarEen oude API-versie die naast de nieuwe blijft draaien, mist de controles die later zijn toegevoegd. Lees hoe aanvallers die omweg gebruiken.
- KwetsbaarhedenCWE-639A01:2021Onvoldoende autorisatie op objectniveau in API'sEen API die alleen controleert óf u bent ingelogd en niet óf dit record van u is, geeft andermans gegevens prijs. Lees hoe u dat afdwingt.
- KwetsbaarhedenCWE-284A01:2021Broken access controlBroken access control uitgelegd: horizontale en verticale escalatie, forced browsing, en hoe u met deny by default serverzijdig autoriseert.
- KwetsbaarhedenCWE-213A01:2021API-antwoorden bevatten te veel gegevensEen API die complete databaserecords teruggeeft en het filteren aan de frontend overlaat, lekt velden die niemand had mogen zien.
- KwetsbaarhedenCWE-269A01:2021Privilege escalationPrivilege escalation uitgelegd: hoe een aanvaller via een role-veld of een onbeschermde adminroute beheerder wordt, en hoe u dat serverzijdig voorkomt.