Prévenir les BOLA dans une API métier
Section intitulée « Prévenir les BOLA dans une API métier »Une BOLA (Broken Object Level Authorization) survient lorsqu’une API (Application Programming Interface, interface de programmation applicative) accepte un identifiant d’objet sans vérifier que l’identité appelante peut accéder précisément à cet objet. Le défaut paraît banal, donc il survit très bien aux revues centrées sur l’authentification, aux tests heureux et aux slides qui promettent du zero trust avec trois cases colorées.
Un guichet peut demander une pièce d’identité à chaque visiteur tout en remettant n’importe quel dossier dès qu’un numéro est prononcé. Le problème ne vient pas du contrôle à l’entrée, mais de l’oubli de vérifier à qui appartient chaque dossier demandé.
→ Lire l’explication complète de la marmotte
BOLA : l’autorisation porte sur l’objet, pas sur la route
Section intitulée « BOLA : l’autorisation porte sur l’objet, pas sur la route »Une authentification valide prouve qu’une identité a présenté un secret, un certificat ou une session acceptable. Elle ne prouve rien sur le droit de cette identité à lire, modifier ou supprimer la ressource désignée par /accounts/8472. Confondre les deux est la racine du problème.
La BOLA est aussi appelée IDOR (Insecure Direct Object Reference, référence directe non sécurisée à un objet). Le nom importe peu : une route contenant un identifiant contrôlé par le client devient vulnérable si le serveur charge l’objet avant de contraindre la requête par le périmètre d’autorisation. Remplacer 8472 par 8473 doit produire un refus, pas les données d’un voisin.
Le compromis central est net. Vérifier l’autorisation au niveau objet coûte des requêtes, des jointures, du cache et une modélisation explicite des relations de propriété. Ne pas la vérifier achète des réponses rapides au prix d’une exfiltration triviale. La recommandation est de payer ce coût systématiquement : l’autorisation objet doit être appliquée dans la couche d’accès aux données ou dans un service de décision commun, jamais laissée à la mémoire des développeurs de chaque contrôleur.
Un contrôle de rôle global ne suffit pas. Un rôle customer peut autoriser la lecture d’un compte, mais ne dit pas quel compte. Un rôle support peut ouvrir certains dossiers selon une délégation temporaire, un tenant, une région ou une finalité. Le contrôle doit donc évaluer au minimum : l’identité, l’action demandée, l’objet, son tenant et les relations métier pertinentes.
Les identifiants non prédictibles, comme les UUID (Universally Unique Identifier, identifiant universellement unique), réduisent l’énumération aveugle. Ils ne corrigent pas une BOLA. Un identifiant UUID exposé dans une liste, un courriel, un journal client ou une réponse antérieure reste réutilisable. L’UUID comme contrôle d’accès est une serrure en carton avec un numéro de série très long.
Pour les fondations d’identité et de session, consulter aussi les principes de sécurité, identité et cryptographie réseau. La couche transport protège la circulation, pas la légitimité de l’accès demandé.
Modèle d’autorisation : tenant, relation et action
Section intitulée « Modèle d’autorisation : tenant, relation et action »Le modèle doit être défini avant de dessiner les routes. Une ressource invoice n’est pas seulement une facture identifiée par id : elle appartient à une organisation, peut être visible par son émetteur et son destinataire, et son téléchargement peut être interdit après archivage. Cette sémantique appartient au modèle d’autorisation, pas à un commentaire oublié au-dessus d’un GET.
Pour une application multi-tenant, toute lecture ou écriture doit être contrainte par tenant_id dérivé de l’identité authentifiée ou d’un contexte serveur vérifié. Accepter tenant_id depuis le corps JSON (JavaScript Object Notation, format textuel de données), un paramètre de requête ou un en-tête non signé recrée la BOLA sous une autre moustache.
Deux stratégies tiennent en production :
- Filtrer directement les requêtes de données par le périmètre autorisé, par exemple
WHERE invoice.id = :id AND invoice.tenant_id = :tenant_id. - Centraliser la décision dans un PDP (Policy Decision Point, point de décision de politique), lorsque les relations sont nombreuses, changeantes ou partagées entre services.
Le filtrage en base est préférable pour les règles de propriété stables et les chemins à fort débit. Il réduit le risque qu’un appelant oublie le contrôle, à condition que tous les accès passent par les mêmes dépôts ou vues. Un PDP devient préférable lorsque l’autorisation dépend de délégations, de hiérarchies, de consentements ou d’attributs externes. Le prix est une dépendance critique supplémentaire, des décisions à mettre en cache et une discipline de cohérence. Une politique distribuée sans journal de décision est surtout un mystère distribué.
Les ACL (Access Control List, liste de contrôle d’accès) par objet conviennent aux partages ponctuels, mais explosent vite en cardinalité. Le RBAC (Role-Based Access Control, contrôle d’accès fondé sur les rôles) convient aux droits de fonction. L’ABAC (Attribute-Based Access Control, contrôle d’accès fondé sur les attributs) convient aux contraintes contextuelles. Aucun modèle ne dispense de vérifier l’objet ciblé. Le choix dépend de la stabilité des relations : rôles pour les fonctions durables, relations explicites pour les partages, attributs pour les décisions contextuelles.
Exemple reproductible : requête bornée par le tenant
Section intitulée « Exemple reproductible : requête bornée par le tenant »L’exemple suivant utilise PostgreSQL et une table invoices. La valeur app.tenant_id est injectée par le serveur après validation du jeton de session, jamais par le client. Une politique RLS (Row-Level Security, sécurité au niveau des lignes) ajoute une barrière de base de données contre l’oubli applicatif.
-- Exécuté par un rôle d'administration lors du déploiement.ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
-- Chaque ligne n'est visible que depuis le tenant placé dans la session SQL.CREATE POLICY invoices_by_tenant ON invoices USING (tenant_id = current_setting('app.tenant_id', true)::uuid) WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);
-- Exécuté au début d'une transaction par le serveur, après authentification.BEGIN;SET LOCAL app.tenant_id = '2f1c0e8d-7d5a-46be-9434-6f37c057d4f1';
-- L'identifiant fourni dans l'URL ne peut pas contourner la politique.SELECT id, amount_cents, statusFROM invoicesWHERE id = 'c3e40287-321a-4e91-89dd-bf35b11ca133';
COMMIT;La RLS n’est pas magique. Le propriétaire de table, les rôles dotés de BYPASSRLS, les connexions d’administration et certaines fonctions SECURITY DEFINER peuvent contourner la politique. Le compte applicatif doit être distinct du propriétaire des schémas, sans privilège de contournement, et les fonctions doivent fixer un search_path strict. Sinon, la barrière existe surtout dans le diagramme d’architecture.
Le pool de connexions impose une précaution supplémentaire : utiliser SET LOCAL dans une transaction ou réinitialiser explicitement le contexte à la restitution de connexion. Une variable de session qui fuit vers la requête suivante peut produire une fuite inter-tenant ou un refus incohérent. Les pools adorent conserver l’état, c’est leur métier. Leur demander de deviner quel état est toxique relève de la divination industrielle.
Surface d’attaque des routes objet
Section intitulée « Surface d’attaque des routes objet »Les attaques BOLA ne se limitent pas au GET /users/{id}. Toute référence contrôlée par le client est concernée : identifiant dans l’URL, corps de requête, en-tête, champ de filtre, fichier joint, clé de stockage, identifiant de tâche asynchrone ou curseur de pagination.
Les vecteurs les plus fréquents sont les suivants :
- Énumération séquentielle : incrémenter un entier révèle les objets voisins. La contre-mesure est l’autorisation objet, pas seulement un identifiant opaque.
- Mass assignment : envoyer
owner_id,tenant_idouapproved_bydans une mise à jour modifie une relation de sécurité. Utiliser des schémas d’entrée distincts des modèles persistés et une liste blanche de champs modifiables. - Endpoints secondaires : une route principale est protégée, mais
/exports/{id}/download,/attachments/{id}ou/jobs/{id}ne l’est pas. Inventorier toutes les références, y compris les liens signés et les webhooks. - Cache partagé : une réponse autorisée pour un utilisateur est servie à un autre parce que la clé de cache ignore l’identité ou le tenant. Les réponses privées doivent porter
Cache-Control: private, no-storelorsque le cache n’est pas maîtrisé, ou une clé intégrant le périmètre d’autorisation. - Confusion lecture-écriture : vérifier le droit de lecture avant une modification permet une élévation de privilège. Chaque verbe métier possède sa propre décision.
Les tests doivent générer au moins deux identités dans deux périmètres distincts, puis rejouer chaque opération avec l’identifiant de l’autre. Tester seulement le propriétaire est un test de disponibilité fonctionnelle, pas un test de sécurité. Les outils DAST (Dynamic Application Security Testing, test dynamique de sécurité applicative) détectent parfois l’énumération, mais ne connaissent pas les règles métier. Les tests d’autorisation doivent être écrits comme des invariants : une identité hors périmètre obtient systématiquement 404 ou 403, sans différence de contenu, de taille ou de temps assez stable pour confirmer l’existence d’un objet.
Ce que le contrôle d’accès objet ne garantit pas
Section intitulée « Ce que le contrôle d’accès objet ne garantit pas »Une autorisation objet correcte ne protège pas une identité compromise. Un attaquant ayant volé une session légitime peut accéder à tous les objets autorisés pour cette session. La protection complémentaire vient de la durée de vie courte des sessions, de la détection d’anomalies, de l’authentification forte et de la révocation lorsque l’architecture le permet. Les sujets d’identités méritent un traitement dédié dans la rubrique identités.
Elle ne protège pas non plus contre l’exfiltration par une fonction autorisée mais excessive. Un endpoint permettant d’exporter toutes les factures de son tenant peut être conforme au modèle objet et pourtant dangereux. Il faut des limites de volume, des exports asynchrones, une validation de finalité métier et des alertes sur les extractions inhabituelles.
Enfin, elle ne remplace ni le chiffrement, ni l’isolation réseau, ni la journalisation. Le protocole TLS (Transport Layer Security, sécurité de la couche transport) évite la lecture du trafic en transit ; il ne décide pas qui possède la facture. Cette confusion revient régulièrement parce qu’un cadenas dans un navigateur est plus photogénique qu’une clause WHERE.
Exploitation : journaux, rotation et reprise
Section intitulée « Exploitation : journaux, rotation et reprise »Chaque décision refusée doit produire un événement structuré contenant l’identité technique, le tenant résolu côté serveur, l’action, le type d’objet, un identifiant pseudonymisé de l’objet et le motif de refus. Ne pas journaliser le contenu sensible ni les jetons de session. Les journaux deviennent vite une seconde base de données, avec une sécurité souvent décorative.
Superviser le taux de refus par route, identité, adresse source et tenant. Une hausse de 403 ou 404 sur des identifiants variés indique souvent une énumération. Un taux nul est également suspect : soit les contrôles sont absents, soit l’observabilité est absente, ce qui est une manière créative de ne jamais être dérangé.
Les secrets de signature des jetons et les identifiants de service doivent résider dans un gestionnaire de secrets, avec rotation testée. La rotation d’une clé de signature impose une période où plusieurs clés publiques ou identifiants de clé sont acceptés, sans prolonger indéfiniment les jetons anciens. La sauvegarde doit inclure les politiques, les migrations RLS, les définitions de rôles et les configurations de PDP. Restaurer les données sans restaurer les règles réouvre parfois des accès avant même que la supervision ait fini son café.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans contrôle d’accès au niveau objet, toute identité authentifiée devient un périmètre d’accès universel : il suffit de substituer un identifiant pour franchir la frontière entre organisations. La conséquence la plus coûteuse est rarement la lecture initiale, mais l’impossibilité de la circonscrire après coup. Faute de décision d’autorisation enregistrée par objet, rien ne distingue une consultation légitime d’une consultation abusive dans les traces, et l’étendue réellement consultée ne peut plus être bornée : le périmètre à notifier devient l’ensemble des enregistrements atteignables. Les données ainsi obtenues se propagent ensuite dans des sous-produits difficiles à assainir, index de recherche, exports, caches et rapports agrégés, qui conservent un mélange inter-organisations bien après la correction du code. La reprise impose alors de reconstruire ces dérivés, pas seulement de déployer un correctif.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Une application reçoit une demande contenant une identité et l’identifiant d’une donnée, par exemple une facture. Elle doit suivre deux vérifications différentes. La première vérifie que l’identité est reconnue, grâce à une session ou à une preuve de connexion. La seconde vérifie que cette identité a le droit précis d’effectuer l’action demandée sur cette facture précise.
Le défaut appelé BOLA apparaît lorsque seule la première vérification est faite. Une personne connectée peut alors changer le numéro de facture dans la demande et obtenir une facture qui ne lui appartient pas. Le serveur doit donc retrouver la facture en limitant la recherche au périmètre de l’identité connectée, comme son organisation, ses droits de partage ou son rôle. Si la donnée n’est pas dans ce périmètre, le serveur refuse la demande sans révéler si elle existe.
Cette vérification doit être faite pour lire, modifier, supprimer, télécharger ou exporter une donnée. Elle doit aussi couvrir les pièces jointes, les tâches lancées en arrière-plan et les listes de résultats. Les numéros difficiles à deviner réduisent les tentatives au hasard, mais ne remplacent pas cette vérification.
Les règles doivent être stockées, testées et surveillées. Les journaux permettent de repérer des essais répétés sur des données interdites. Si une règle disparaît, si un compte technique contourne les protections ou si une session est volée, l’accès peut échouer ou devenir abusif. Le contrôle doit donc être complété par la protection des sessions, des secrets, des sauvegardes et des données transmises.
À lire aussi
- DAC, MAC, RBAC, ABAC ou ReBAC : quel modèle choisirComparer les modèles de contrôle d'accès DAC, MAC, RBAC, ABAC et ReBAC selon la question à laquelle l'autorisation doit répondre, puis choisir et combiner.
- Masquage dynamique pour tests et développementMettre en place un masquage dynamique de données pour conserver des jeux exploitables en test et développement sans distribuer les valeurs sensibles.
- Mettre en œuvre les revues d’accès IGA et la SoDGouverner le cycle de vie des identités, les revues d’accès, les modèles RBAC, ABAC et ReBAC, ainsi que la séparation des tâches.
- Comment empêcher une attaque de fixation de sessionDémonstration en laboratoire de l’attaque de fixation de session et contre-mesure par régénération de l’identifiant après authentification.
Pages voisines
La citent
- Comment corriger une attaque path traversal
- Contourner un contrôle proxy par confusion de chemin
- Attributs du cookie de session
- Comment minimiser les données collectées
- DAC, MAC, RBAC, ABAC ou ReBAC : quel modèle choisir
- Fuite des instructions d'un assistant IA personnalisé publié
- JavaScript et SQL côté web, ce qu'il faut savoir des deux langages
- OWASP Top 10 ASVS ZAP et sécurité applicative
- Pièges OAuth 2.1 : PKCE, redirections et scopes
- Pourquoi les fuites de données persistent malgré la sécurité
Prévenir les BOLA dans une API métier
Elle cite
: les deux pages se citent l’une l’autre.
