L'IDOR (CWE-639, OWASP A01:2021) survient quand une application accède à des objets sans vérifier l'autorisation — #1 cause de violations de données SaaS.
TL;DR
Une référence directe à un objet non sécurisée (IDOR) survient quand une application utilise une entrée contrôlée par l'utilisateur — segment de chemin d'URL, paramètre de requête, champ de corps de requête, ou en-tête HTTP — pour chercher un objet interne directement, sans vérifier que l'utilisateur authentifié est autorisé à accéder à cet objet spécifique. L'application authentifie correctement l'utilisateur mais omet la deuxième étape : confirmer que l'identité authentifiée possède ou s'est vu accorder l'accès à la ressource demandée.
L'IDOR est classifié CWE-639 (Authorization Bypass Through User-Controlled Key) et constitue la principale classe de vulnérabilité au sein d'OWASP A01:2021 et A01:2025 — Contrôle d'accès défaillant. Dans le contexte de la sécurité API, son équivalent se nomme BOLA (Broken Object Level Authorization), position API1:2023 de l'OWASP API Security Top 10. La distinction est terminologique : IDOR et BOLA décrivent la même cause racine appliquée dans des contextes web et API respectivement.
La vulnérabilité persiste à grande échelle car la plupart des frameworks d'authentification imposent correctement l'identité, mais l'autorisation au niveau objet — vérifier que cette identité peut accéder à cet enregistrement spécifique — nécessite une implémentation délibérée par endpoint. Quand les développeurs supposent que l'authentification est suffisante, ou quand la logique d'autorisation est appliquée dans l'interface utilisateur mais pas dans la couche API, l'IDOR en résulte.
Le gap d'autorisation a trois causes racines dans le code de production :
WHERE owner_id = ?.role, is_admin, tenant_id) que l'utilisateur ne devrait jamais contrôler.# VULNÉRABLE — authentifie mais n'autorise pas
@router.get("/api/factures/{facture_id}")
async def obtenir_facture(facture_id: int, user: User = Depends(get_current_user)):
facture = await db.get(Facture, facture_id) # récupère N'IMPORTE QUELLE facture
return facture
# SÉCURISÉ — propriété imposée au niveau requête de base de données
@router.get("/api/factures/{facture_id}")
async def obtenir_facture(facture_id: int, user: User = Depends(get_current_user)):
facture = await db.execute(
select(Facture)
.where(Facture.id == facture_id)
.where(Facture.owner_id == user.id) # verrou de propriété
)
if not facture:
raise HTTPException(status_code=404) # 404 pas 403 — évite l'oracle d'existence
return facture| Variante | Mécanisme principal | Impact typique | Plage CVSS |
|---|---|---|---|
| Référence directe à un objet | ID entier séquentiel dans l'URL/le corps — incrémenter pour accéder aux enregistrements pairs | Exposition de PII, données financières | 6,5–8,8 |
| Escalade horizontale de privilèges | Utilisateur du même rôle accède aux ressources d'un pair en substituant son identifiant | Accès données inter-utilisateurs, écriture BFLA | 6,5–8,8 |
| Escalade verticale de privilèges | Utilisateur normal appelle des endpoints admin ou obtient des références d'objets admin | Compromission système, prise de contrôle de compte | 7,5–9,1 |
| Référence indirecte à un objet | Token opaque (hash, UUID v1, slug) mappant un enregistrement mais devinable ou réversible | Variable selon le type de référence | 5,0–7,5 |
| IDOR via Mass Assignment | Les champs du corps de requête se lient aux propriétés protégées du modèle — role, is_admin, balance | Escalade de privilèges, fraude financière | 7,5–9,0 |
La forme la plus simple : les clés primaires auto-incrémentées apparaissent dans les URLs ou les corps de requête. Un attaquant incrémente ou décrémente l'ID pour accéder aux enregistrements adjacents. La violation Optus (2022) a exploité exactement ce pattern — GET /api/customers/1 jusqu'à GET /api/customers/9800000 a exfiltré 9,8 millions d'enregistrements sans authentification.
L'utilisateur A et l'utilisateur B partagent le même rôle. L'utilisateur A substitue l'identifiant de ressource de B dans une requête authentifiée comme A. L'application valide la session de A mais ne vérifie pas si A possède la ressource référencée. Il s'agit du sous-type IDOR le plus courant et il représente la majorité des rapports bug bounty de haute sévérité. La violation McHire de McDonald's (juillet 2025, 64 millions d'enregistrements) était un IDOR horizontal sur le paramètre lead_id.
Un utilisateur normal invoque des endpoints ou fonctions réservés aux rôles privilégiés. Contrairement à l'IDOR horizontal — qui concerne l'accès aux données d'un pair — l'escalade verticale concerne l'invocation de fonctions privilégiées. CVE-2025-27507 dans la plateforme d'identité ZITADEL (CVSS 9,0) permettait à des utilisateurs non-admin d'appeler 12 endpoints gRPC de l'Admin API, incluant la modification de la configuration LDAP, atteignant la prise de contrôle totale de compte pour tous les utilisateurs LDAP de l'instance.
L'application utilise un identifiant secondaire — un nom de fichier, code de référence, hash ou token opaque — qui semble impossible à deviner mais est réversible ou énumérable. md5(user_id) est trivialement calculable. Les IDs encodés en Base64 se décodent immédiatement. Les horodatages UUID v1 sont prévisibles dans une fenêtre temporelle. Le point essentiel : l'opacité n'est pas une autorisation.
Les frameworks API qui lient les champs du corps de requête directement aux objets du modèle ORM permettent aux attaquants de définir des champs que l'application n'a jamais eu l'intention de rendre inscriptibles par l'utilisateur : role, is_admin, account_balance, tenant_id. CVE-2024-7297 dans Langflow (CVSS 8,8) permettait à tout utilisateur authentifié d'obtenir un accès super-admin en envoyant {"is_superuser": true} à un endpoint PATCH.
Optus (septembre 2022) — Un endpoint REST /api/customers/{id} retournait des enregistrements clients indexés par entiers séquentiels sans authentification. GET /api/customers/1 jusqu'à 9,8 millions d'itérations a exfiltré noms, dates de naissance, numéros de passeport et de permis de conduire. Résultat réglementaire : amende ACMA de 1,36 million AUD.
McDonald's McHire (juillet 2025) — Les chercheurs Ian Carroll et Sam Curry ont découvert que le paramètre lead_id sur /api/lead/cem-xhr était un entier séquentiel. Le décrémenter ou l'incrémenter retournait le transcript de chat complet d'un autre candidat incluant nom, téléphone, email et résultats de tests de personnalité. Échelle : 64 millions d'enregistrements. Remédié dans les 24 heures suivant la divulgation.
CVE-2025-27507 — ZITADEL (CVSS 9,0, Critique) — 12 endpoints HTTP de l'Admin API imposaient des permissions IAM au niveau organisation au lieu du niveau système. Des utilisateurs non-admin authentifiés modifiaient la configuration LDAP, redirigeant l'authentification vers un serveur malveillant et interceptant les credentials de tous les utilisateurs LDAP de l'instance. Affectait ZITADEL 2.x sur huit branches de versions.
HackerOne #2122671 — Plateforme HackerOne (12 500 $) — Une mutation GraphQL permettait la suppression des certifications et licences de n'importe quel utilisateur. Le champ id de l'objet dans la mutation n'était pas validé contre l'identité de l'appelant. La plateforme qui gère les programmes bug bounty était elle-même vulnérable à l'IDOR.
HackerOne #415081 — PayPal (10 500 $) — La manipulation de paramètre sur /businessmanage/users/api/v1/users accordait la création de compte secondaire sur les comptes business des victimes, permettant la prise de contrôle de compte.
/api/commandes/1338), chaîne de requête (?order_id=1338), corps de requête ({"order_id": 1338}), et en-têtes HTTP (X-User-ID: 1338).role, is_admin, balance). Faire suivre d'un GET pour vérifier la persistance.L'extension Burp Suite Autorize rejoue automatiquement chaque requête interceptée avec le cookie de session d'un second utilisateur, colorant les réponses selon l'état du contrôle d'accès. AuthMatrix définit une matrice de permissions rôle × endpoint et teste toutes les combinaisons. Les outils automatisés ont une limitation fondamentale : ils ne peuvent pas raisonner sur la propriété d'objet sans relations de ressources définies par un humain.
BreachVex détecte l'IDOR via une approche différentielle multi-signaux : il sonde les IDs adjacents, compare les corps de réponse entre deux sessions authentifiées, et corrobore les findings par des signaux complémentaires — un différentiel de code de statut (403→200), echo d'ID dans le corps de réponse, divergence de champs PII, et comparaison de structure JSON. La détection de mass assignment injecte une large liste de champs de privilège dans les endpoints POST/PUT/PATCH et confirme via une re-lecture GET.
Retourner HTTP 403 sur un accès non autorisé confirme l'existence de la ressource, créant des oracles d'énumération. Retournez toujours 404 pour un accès à un objet non autorisé — cela force les attaquants à distinguer "n'existe pas" de "existe mais interdit" sans signal déterministe.
Dériver l'identité utilisateur exclusivement de la session authentifiée. Chaque requête sur une ressource appartenant à un utilisateur doit inclure la contrainte de propriété structurellement — pas comme une vérification optionnelle qu'un développeur futur pourrait omettre.
-- VULNÉRABLE — récupère n'importe quel enregistrement par ID
SELECT * FROM orders WHERE order_id = ?;
-- SÉCURISÉ — la propriété est une contrainte structurelle de requête
SELECT * FROM orders WHERE order_id = ? AND owner_id = ?;
-- Multi-tenant : ajouter le scope tenant
SELECT * FROM orders WHERE order_id = ? AND owner_id = ? AND tenant_id = ?;Ne pas exposer d'entiers auto-incrémentés dans les API publiques. L'UUID v4 (cryptographiquement aléatoire, 2¹²² valeurs possibles) prévient l'IDOR basé sur l'énumération. L'UUID v1 (dérivé d'un horodatage) est partiellement prévisible et doit être évité pour les tokens de sécurité (CVE-2024-45719). Les UUID ne remplacent pas l'autorisation — ils sont des contrôles de limitation du débit sur les tentatives de devinette.
Utiliser la validation de schéma qui déclare exactement quels champs sont inscriptibles. Les champs absents du schéma ne peuvent pas être soumis quel que soit ce qu'envoie le client.
# FastAPI / Pydantic — seuls les champs déclarés sont inscriptibles
class DemandeMAJUtilisateur(BaseModel):
nom_affiche: str
bio: str | None = None
# is_admin, role, tenant_id : NON déclarés → rejetés automatiquement
@router.patch("/utilisateurs/moi")
async def maj_utilisateur(user_id: str = Depends(get_current_user_id),
data: DemandeMAJUtilisateur):
await db.users.update(id=user_id, **data.model_dump(exclude_unset=True))RLS impose la propriété au niveau de la base de données, empêchant l'IDOR même si le code applicatif présente un défaut :
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY orders_isolation ON orders
USING (owner_id = current_setting('app.current_user_id')::int);
-- Dans la configuration de connexion FastAPI : SET app.current_user_id = {user.id}Le papier Zanzibar de Google (2019) a introduit le contrôle d'accès basé sur les relations (ReBAC) — modélisant l'accès comme des relations de graphe entre utilisateurs et ressources. Les implémentations open-source incluent OpenFGA (CNCF Incubating), SpiceDB et Warrant. ReBAC prévient naturellement l'IDOR inter-tenant car l'accès est défini par des arêtes de relation explicites, pas par la correspondance de paramètres.
Une vulnérabilité IDOR survient quand une application utilise une entrée contrôlée par l'utilisateur — paramètre d'URL, champ de corps de requête, valeur d'en-tête — pour accéder à un objet interne (enregistrement de base de données, fichier, ressource API) sans vérifier que le demandeur est autorisé à accéder à cet objet spécifique.
L'IDOR (référence directe à un objet non sécurisée) est la vulnérabilité web générale. BOLA (Broken Object Level Authorization) est son équivalent API, position API1:2023 du classement OWASP API Security Top 10. Les deux décrivent la même cause racine : validation manquante de propriété côté serveur sur l'accès au niveau objet.
Oui. L'IDOR relève d'OWASP A01:2025 — Contrôle d'accès défaillant, qui conserve la première place. L'OWASP recense 1 839 701 occurrences et indique que 100 % des applications testées présentent une forme de contrôle d'accès défaillant.
Les outils DAST ratent la plupart des IDOR car la détection requiert une perspective inter-utilisateurs — deux sessions authentifiées distinctes testant les ressources l'une de l'autre. La recherche Semgrep 2025 indique que le meilleur LLM atteint seulement 22 % de vrais positifs sur la détection IDOR. Le test manuel à deux comptes reste la référence.
Non. L'UUID v4 (aléatoire) empêche l'IDOR basé sur l'énumération mais ne protège pas contre l'accès une fois qu'un attaquant obtient légitimement une référence. L'UUID v1 (dérivé d'un horodatage) est partiellement prévisible via des attaques sandwich (CVE-2024-45719). Les vérifications de propriété côté serveur restent toujours obligatoires.
La violation Optus (2022) — un IDOR classique sur /api/customers/{id} — a exposé 9,8 millions d'enregistrements et entraîné une amende réglementaire de 1,36 million AUD. La violation McHire de McDonald's (juillet 2025) a exposé 64 millions de candidats. La remédiation moyenne d'un IDOR coûte environ 25 000 $ (prime + ingénierie).
L'IDOR seul expose des données. Les attaques chaînées le rendent critique : IDOR pour lire un token de réinitialisation → prise de contrôle de compte ; IDOR + mass assignment pour écrire role=admin → escalade de privilèges ; IDOR pour lire un cookie de session → détournement de session. La chaîne, pas la lecture initiale, détermine la sévérité critique.
Dans les architectures multi-tenant, un IDOR qui franchit les limites de tenant expose les données d'une organisation entière plutôt que celles d'un seul utilisateur. Les scores CVSS atteignent systématiquement 8,8–9,5 pour les IDOR inter-tenant. Le contexte tenant doit être dérivé de la session authentifiée, jamais des paramètres de requête.