Verander in de URL ordernummer 1042 in 1043 en zie je andermans bestelling? Dan heb je broken access control, de nummer één in de OWASP Top 10:2025.
Verander in de URL ordernummer 1042 in 1043 en zie je andermans bestelling? Dan heb je broken access control, de nummer één in de OWASP Top 10:2025.
Broken access control (A01) staat bovenaan de OWASP Top 10:2025, inclusief SSRF. Het is geen exotische aanval. De tester hoeft alleen een nummer te veranderen. In een webshop is de impact direct: namen, adressen, telefoonnummers, bestelhistorie. In dit artikel laten we zien waar het misgaat, hoe je het test met twee accounts en hoe je het structureel oplost in Magento en Laravel. Het OWASP-overzicht staat op /security/owasp.
Wat is IDOR?
IDOR betekent Insecure Direct Object Reference. De applicatie gebruikt een identifier uit het verzoek (order-ID, factuurnummer, adres-ID) om een object op te halen, zonder te controleren of de ingelogde gebruiker dat object mag zien.
Authenticatie is dan wel in orde (je bent ingelogd), maar autorisatie ontbreekt (je mag dit specifieke object niet). Dat verschil is de kern. Veel developers bouwen een nette login en vergeten de tweede vraag: van wie is dit?
IDOR is een vorm van broken access control. Andere vormen:
- Verticale escalatie. Een gewone klant bereikt een admin- of manager-functie.
- Horizontale escalatie. Klant A bereikt gegevens van klant B (dit is IDOR).
- Ontbrekende functie-controle. Een endpoint voor een actie (terugbetalen, exporteren) is verborgen in de UI, maar niet afgeschermd op de server.
- Forceful browsing. Pagina's die niet gelinkt worden, maar wel bereikbaar zijn.
Waar zien we het in webshops?
Hieronder de plekken waar het in audits en codereviews vaak speelt. Dit zijn patronen, geen specifieke klantcases.
Order-ID in de URL. Een pagina als/account/orders/view/1042. Als de controller de order laadt op ID en alleen controleert of je ingelogd bent, zie je elke order.
Factuur-PDF's en credit notes. Een downloadlink als /invoice/download?id=5531. Documenten worden vaak buiten de normale pagina-controller bediend en daardoor vergeten bij de rechtencheck. Hetzelfde geldt voor pakbonnen, retourlabels en exports.
Adresboek en betaalgegevens. Een verzoek om address_id=77 te wijzigen of te verwijderen, zonder controle of dat adres bij jouw account hoort.
Guest-orders. Een succespagina of trackingpagina die bereikbaar is met een voorspelbaar nummer. Bij gastbestellingen is er geen account; het geheim moet dus in de link zitten (een lang, onvoorspelbaar token), niet in een oplopend nummer.
B2B company accounts. Hier is het ingewikkelder. Een bedrijf heeft meerdere kopers met rollen: inkoper, manager, financieel. Mag een inkoper de orders van een collega zien? Mag hij goedkeuren? Elke combinatie van rol en object is een kans op een fout. Zie ook Magento B2B-rollen en rechten.
API-endpoints. De webinterface is netjes, maar het onderliggende REST- of GraphQL-endpoint doet geen eigenaarscheck. Mobiele apps en headless voorkanten praten direct met de API. Zie API-beveiliging voor e-commerce integraties.
Admin-functies zonder rolcheck. Een gebruiker met beperkte rechten roept via een URL een actie aan die niet in zijn menu staat. In Magento regel je dit met ACL-resources; zie Magento admin beveiligen.
Zoek- en exportfuncties. Een filter dat toevallig resultaten van alle klanten teruggeeft, of een CSV-export zonder scoping.
Waarom voorspelbare ID's het erger maken
Een oplopend nummer maakt misbruik bulkwerk. Een script loopt van 1 tot 100.000 en haalt alles op. Met willekeurige identifiers (UUID's, lange tokens) is raden praktisch onmogelijk.
Maar let op: een onvoorspelbaar ID is geen vervanging voor een autorisatiecheck. Het maakt misbruik moeilijker, het lost de fout niet op. Als het ID ergens uitlekt (e-mail, logs, referrer, delen van een link), is het object nog steeds open. Doe beide: ownership-check en niet-raadbare ID's waar het kan.
Zo test je het zelf met twee accounts
Je hebt geen dure tools nodig voor de eerste ronde. Je hebt twee testaccounts nodig, bij voorkeur op staging.
- Maak account A en account B. Plaats met beide een bestelling, voeg een adres toe, genereer een factuur.
- Log in als A en volg een actie, bijvoorbeeld het bekijken van een order. Open de netwerktab in je browser en kopieer het verzoek met de identifier.
- Log in als B en speel het verzoek van A af, bijvoorbeeld door het order-ID van A in een sessie van B te gebruiken.
- Krijg je de gegevens van A terug? Dan is er een IDOR. Een correcte respons is 403 of 404.
- Herhaal voor elke plek met een identifier: orders, facturen, adressen, retouren, wishlists, API-calls, downloads.
- Test ook zonder inloggen. Werkt het verzoek zonder sessie?
- Test schrijfacties: wijzigen, verwijderen, annuleren. Die zijn vaak slechter afgeschermd dan lezen.
- Test met een rol met minder rechten (bij B2B): kan een inkoper doen wat alleen een manager mag?
Sorteer de uitkomst naar impact: persoonsgegevens en geld eerst. Wil je dit professioneel laten doen, dan hoort het standaard in een grey-box pentest; zie wat een pentest kost en wat je krijgt.
Oplossen in Magento
Magento geeft je gereedschap, maar je moet het gebruiken.
- Gebruik het klantcontext. In controllers voor de klantomgeving haal je de huidige klant uit de sessie (customer session) en vergelijk je die met de eigenaar van het object. Vertrouw nooit op een klant-ID uit het verzoek.
- Laad objecten via repositories en controleer ownership. Haal de order op en vergelijk het
customer_idmet de ingelogde klant. Geen match? Geef een 404 terug, geen 403. Een 404 verraadt niet dat het object bestaat. - Let op custom endpoints. In eigen REST-routes (
webapi.xml) bepaal je in welke resource of rol het endpoint valt. Hetself-resource-pad is bedoeld voor eigen gegevens van de klant. Een route metanonymousis voor iedereen bereikbaar. - Admin-ACL. Elke admin-controller heeft een
ADMIN_RESOURCEnodig die klopt. Een verkeerde of te brede resource geeft meer rechten dan bedoeld. - Extensies reviewen. Third-party code is een bekende bron van autorisatiefouten, omdat het eigen controllers en endpoints toevoegt. Zie extensies en supply chain.
Een eenvoudige ownership-check in een controller ziet er zo uit:
$order = $this->orderRepository->get($orderId);
if ((int) $order->getCustomerId() !== (int) $this->customerSession->getCustomerId()) {
throw new \Magento\Framework\Exception\NoSuchEntityException();
}
Dit is een vereenvoudigd voorbeeld. In productie plaats je zo'n check bij voorkeur op één centrale plek, zodat elke nieuwe controller hem automatisch krijgt.
Oplossen in Laravel
Laravel heeft hier een sterk raamwerk voor: policies en gates.
- Policies per model. Definieer in een
OrderPolicywie een order mag bekijken, wijzigen of verwijderen. In de controller roep je$this->authorize('view', $order)aan, of gebruik jeGate::authorize. - Scope je queries. Haal data op via de relatie van de ingelogde gebruiker:
$request->user()->orders()->findOrFail($id). Dan bestaat een andermans order voor jouw query niet. Dit is vaak de simpelste en veiligste vorm. - Route model binding met scope. Gebruik gescoopte bindings zodat een geneste resource alleen wordt gevonden binnen de eigenaar.
- Filament en admin-panelen. Gebruik policies ook daar, en test of een gebruiker met beperkte rechten niet via een directe URL bij andere resources kan. Zie de Filament beveiligingschecklist.
- API-resources. Geef alleen velden terug die de gebruiker mag zien. Mass assignment en te rijke responses lekken ook data.
Een voorbeeld van een gescoopte query:
$order = $request->user()->orders()->findOrFail($id);
Meer Laravel-specifieke punten: Laravel security checklist voor productie en /security/laravel.
Shopify en andere platformen
Op Shopify beheert het platform klantaccounts, orderpagina's en checkout. Je maakt IDOR-fouten vooral in eigen apps, headless voorkanten en middleware die Shopify-data doorgeeft. Gebruik daar dezelfde regels: haal data op namens de ingelogde klant, niet op basis van een ID uit het verzoek. Zie /security/shopify.
Structurele maatregelen
Los een IDOR nooit alleen op die ene plek op. Zorg dat het patroon niet terugkomt.
- Centraliseer autorisatie. Eén plek voor ownership-checks (policy, service, base controller).
- Deny by default. Nieuwe endpoints zijn dicht totdat ze expliciet worden opengezet.
- Review-checklist. Elke pull request met een nieuw endpoint krijgt de vraag: van wie is dit object, en waar controleren we dat?
- Geautomatiseerde tests. Schrijf tests met twee gebruikers: gebruiker B mag het object van A niet krijgen. Eén test per endpoint is goedkoop.
- Logging. Veel 403- en 404-responses van één gebruiker op oplopende ID's wijzen op iemand die aan het raden is. Zie incident response en monitoring.
- Rate limiting. Een WAF of applicatielimiet maakt bulkwerk lastiger. Zie WAF en bot-bescherming.
Rate limiting is een vertraging, geen oplossing. De autorisatiecheck blijft de fix.
Wat als je het al gevonden hebt?
Was een IDOR live bereikbaar, dan is er mogelijk een datalek. Bekijk je logs: zijn er verzoeken met veel opeenvolgende ID's? Is er risico voor betrokkenen, dan geldt de meldplicht bij de Autoriteit Persoonsgegevens binnen 72 uur (AVG art. 33). Dit is geen juridisch advies; schakel bij twijfel je functionaris gegevensbescherming of jurist in.
Veelgestelde vragen
Wat is het verschil tussen authenticatie en autorisatie?
Authenticatie bewijst wie je bent (inloggen). Autorisatie bepaalt wat je mag doen of zien. Bij IDOR werkt authenticatie, maar de controle of een object bij jou hoort, ontbreekt.
Is een UUID in plaats van een oplopend ID genoeg?
Nee. Een UUID maakt raden moeilijk, maar lost de ontbrekende autorisatiecheck niet op. Lekt de identifier uit, dan is het object nog steeds open. Gebruik beide: ownership-check en niet-raadbare ID's.
Hoe test ik zelf op IDOR?
Maak twee klantaccounts, speel een verzoek van account A af in de sessie van account B, en kijk of je gegevens van A ziet. Doe dat voor orders, facturen, adressen, downloads en API-calls, ook zonder inloggen en ook voor schrijfacties.
Geeft Magento standaard bescherming tegen IDOR?
De kern van Magento controleert eigenaarschap bij standaard klantpagina's. Het risico zit vooral in maatwerk, third-party extensies en eigen API-endpoints. Daar moet je de autorisatie zelf correct inrichten.
Wat doe ik als er al data is uitgelekt?
Sluit het gat, bekijk de logs op misbruik, en beoordeel of er risico is voor betrokkenen. Bij risico meld je het binnen 72 uur bij de Autoriteit Persoonsgegevens. Raadpleeg bij twijfel een jurist.
Wil je weten of jouw shop dit probleem heeft? Wij testen het in een pentest en lossen het met je op. Neem contact op en je spreekt direct met degene die de code leest.

Geschreven door Ruthger Idema
15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.
Meer over ons team →


