Un ID numérique ou séquentiel dans l'URL ou le corps de la requête référence directement une ressource serveur sans vérification d'autorisation.
TL;DR
L'IDOR de référence directe est la forme fondamentale de cette classe de vulnérabilité : une application expose une clé primaire de base de données numérique ou séquentielle dans un emplacement visible par l'utilisateur — segment de chemin d'URL, paramètre de requête, champ de corps de requête, ou en-tête HTTP — et utilise cette valeur fournie par le client comme clé de recherche directe en base de données sans vérifier que l'utilisateur authentifié possède l'enregistrement référencé.
La vulnérabilité persiste car les IDs séquentiels sont la valeur par défaut dans toutes les principales bases de données relationnelles (PostgreSQL SERIAL, MySQL AUTO_INCREMENT, SQLite AUTOINCREMENT). Les développeurs implémentent correctement l'authentification mais omettent la deuxième étape d'autorisation : confirmer que cette identité peut accéder à cet enregistrement spécifique. Le résultat est que l'authentification et l'autorisation semblent synonymes jusqu'à ce qu'un testeur de sécurité fournisse l'ID de quelqu'un d'autre.
CWE-639 (Authorization Bypass Through User-Controlled Key) est la classification précise. OWASP A01:2021 et A01:2025 positionnent tous deux le contrôle d'accès défaillant — dont l'IDOR direct est le sous-type le plus répandu — comme la catégorie de vulnérabilité web la plus risquée. L'OWASP API Security Top 10 positionne BOLA (l'équivalent API) à API1:2023, inchangé depuis 2019, car le pattern reste aussi courant que jamais malgré sa prévention triviale.
L'attaque a quatre points d'injection, tous devant être testés indépendamment :
1. Segment de chemin d'URL :
GET /api/commandes/1046 HTTP/1.1
Host: cible.com
Authorization: Bearer <token_attaquant>2. Paramètre de chaîne de requête :
GET /api/commandes?order_id=1046 HTTP/1.1
Host: cible.com
Authorization: Bearer <token_attaquant>3. Corps de requête (PUT/PATCH/POST) :
PATCH /api/commandes/update HTTP/1.1
Content-Type: application/json
{"order_id": 1046, "status": "annulé"}4. En-tête HTTP :
GET /api/tableau-de-bord HTTP/1.1
X-User-ID: 1046
Authorization: Bearer <token_attaquant>La stratégie d'énumération s'adapte au format de l'ID :
| Format d'ID | Technique d'énumération | Difficulté |
|---|---|---|
Entier séquentiel (1337) | Boucle +1, -1, ou ±100, ±1000 | Trivial |
Chaîne séquentielle (FAC-2024-04521) | Extraire le suffixe numérique, incrémenter | Facile |
Encodé en Base64 (eyJ1c2VyX2lkIjoxMzM3fQ==) | Décoder → modifier l'entier → ré-encoder | Facile |
Adresse email (?email=victime@corp.com) | OSINT/dictionnaire | Facile |
Nom de fichier (avatar_jean_martin.jpg) | Énumération de noms d'utilisateurs d'abord | Moyen |
| UUID v1 (horodatage) | Partiel — attaque sandwich sur l'heure de création | Moyen |
| UUID v4 (aléatoire) | Infaisable sans fuite | Difficile |
| MD5/SHA1(id) | Précalculer md5(1..10000000) | Difficile mais fini |
| Variante | Technique | Impact |
|---|---|---|
| Énumération GET | Incrémenter l'ID dans les requêtes GET | Lecture complète de PII |
| IDOR en écriture | PUT/PATCH avec l'ID d'un autre utilisateur | Modification de données |
| IDOR en suppression | DELETE avec l'ID d'un autre utilisateur | Destruction de données |
| IDOR en masse/batch | Tableau d'IDs dans un seul corps de requête | Exfiltration massive |
| IDOR non authentifié | Aucun token de session requis | Impact maximal |
| Second ordre (stocké) | ID soumis maintenant, consommé dans une requête ultérieure | Contourne le DAST |
L'IDOR en masse mérite une attention particulière : certaines API acceptent des tableaux d'IDs pour réduire les allers-retours HTTP. Une seule requête peut exfiltrer des milliers d'enregistrements et contourne souvent la limitation de débit car elle compte comme une requête :
POST /api/documents/recuperer
Authorization: Bearer <token_attaquant>
{"document_ids": [1001, 1002, 1003, 5000, 9999, 50000]}Si des documents appartenant à différents utilisateurs sont retournés, il s'agit d'un IDOR massif confirmé exfiltrant tous les enregistrements référencés en un seul appel.
Violation de données Optus (septembre 2022) — Un endpoint REST public /api/customers/{id} retournait des enregistrements clients indexés par entiers séquentiels sans authentification. Les attaquants ont émis GET /api/customers/1 jusqu'à GET /api/customers/9800000 itérativement, extrayant noms, dates de naissance, adresses et numéros de passeport de 9,8 millions d'Australiens. Le correctif avait été appliqué au domaine principal en 2021 après un audit interne mais n'avait jamais été propagé à un sous-domaine secondaire resté accessible de l'extérieur. Résultat réglementaire : amende de 1,36 million AUD de l'ACMA.
CVE-2025-13526 — WordPress OneClick Chat to Order (Non authentifié, 2025) — Les versions du plugin WordPress OneClick Chat to Order jusqu'à 1.0.8 inclus acceptaient le paramètre order_id dans l'endpoint de détails de commande du plugin sans aucune authentification ni validation de propriété. N'importe quelle requête HTTP non authentifiée pouvait récupérer les détails complets de commande incluant noms de clients, adresses email, numéros de téléphone, adresses de facturation et de livraison, articles commandés, prix et métadonnées de méthode de paiement. La vulnérabilité était exploitable par n'importe quel client connecté à internet — aucun compte requis.
McDonald's McHire AI Chatbot (juillet 2025) — Le paramètre lead_id sur l'endpoint interne /api/lead/cem-xhr était un entier séquentiel. Les chercheurs Ian Carroll et Sam Curry ont démontré que le décrémenter ou l'incrémenter retournait le transcript de chat complet d'un autre candidat à l'emploi : nom, téléphone, email, préférences de poste, résultats de tests de personnalité et tokens de session réutilisables pour usurper l'identité de candidats sur la plateforme. Exposition estimée : 64 millions de candidats dans tous les McDonald's utilisant McHire.
CVE-2024-55471 — Oqtane Framework (décembre 2024) — Le CMS ASP.NET Core UserController.Get n'effectuait aucune vérification d'autorisation sur le paramètre id. Tout utilisateur authentifié récupérait le profil de n'importe quel autre utilisateur en incrémentant id. Le correctif a été livré dans Oqtane v6.0.1.
# Burp Suite Intruder — fuzzing d'ID séquentiel
# Définir le paramètre ID comme point d'injection
# Type de payload : Nombres (séquentiel de 1000 à 2000, pas de 1)
# Rechercher dans la réponse : email, nom, adresse, téléphone
# Équivalent ligne de commande avec curl
for id in $(seq 1040 1060); do
echo "Test ID $id :"
curl -s -H "Authorization: Bearer $TOKEN_A" \
"https://cible.com/api/commandes/$id" | jq '.owner_email'
doneL'extension Burp Suite Autorize rejoue chaque requête authentifiée interceptée en utilisant le cookie de session d'un second utilisateur. Les réponses sont codées par couleur : rouge indique probablement vulnérable (accès accordé), vert indique protégé. Cela couvre automatiquement les quatre catégories de points d'injection pour le trafic intercepté.
Nuclei dispose de templates communautaires pour l'énumération d'ID séquentiel sur les patterns API courants. Lancer avec -t exposures/ids/ pour les templates spécifiques aux IDs.
BreachVex détecte l'IDOR direct en sondant les endpoints portant des IDs à des deltas séquentiels autour de l'ID de référence (±1, ±2, ±5, ±10, ±100, ±1000). La confirmation requiert au moins deux signaux corroborants : contournement de code de statut (403→200), echo d'ID dans le corps de réponse, divergence de champs PII, ou correspondance de structure JSON avec des valeurs différentes. CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N (base 6,5, escalade à 8,8 avec accès en écriture).
Les réponses soft-404 masquent certaines tentatives d'IDOR : les applications Django et Flask peuvent retourner HTTP 200 avec "Enregistrement non trouvé" dans le corps pour les IDs invalides. Filtrer les réponses pour des marqueurs comme "not found", "does not exist", "invalid id" avant de conclure que l'endpoint est protégé. Un statut 200 seul ne confirme pas l'accès.
Le correctif fondamental : chaque requête de base de données sur une ressource appartenant à un utilisateur doit inclure la contrainte de propriété comme exigence structurelle. La vérification de propriété ne peut pas être omise par erreur si elle fait partie de la requête elle-même.
# FastAPI / SQLAlchemy — VULNÉRABLE
@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
# FastAPI / SQLAlchemy — SÉCURISÉ
@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é structurel
)
if not facture:
raise HTTPException(status_code=404) # jamais 403
return facture// Express / Sequelize — VULNÉRABLE
router.get('/api/commandes/:id', authentifier, async (req, res) => {
const commande = await Order.findByPk(req.params.id); // n'importe quelle commande
res.json(commande);
});
// Express / Sequelize — SÉCURISÉ
router.get('/api/commandes/:id', authentifier, async (req, res) => {
const commande = await Order.findOne({
where: {
id: req.params.id,
userId: req.user.id // propriété imposée au niveau requête
}
});
if (!commande) return res.status(404).json({ erreur: 'Non trouvé' });
res.json(commande);
});Les entiers auto-incrémentés ne doivent pas être exposés dans les API publiques. UUID v4 élève la barre d'énumération de "boucle triviale" à "force brute infaisable" (2¹²² valeurs possibles) :
import uuid
from sqlalchemy import Column, String
class Facture(Base):
# VULNÉRABLE — entier séquentiel exposé dans l'API
id = Column(Integer, primary_key=True) # usage interne BD uniquement
# SÉCURISÉ — UUID aléatoire comme identifiant public
public_id = Column(String, default=lambda: str(uuid.uuid4()), unique=True)
# L'API expose public_id, jamais id
@router.get("/api/factures/{public_id}")
async def obtenir_facture(public_id: str, user: User = Depends(get_current_user)):
facture = await db.execute(
select(Facture)
.where(Facture.public_id == public_id)
.where(Facture.owner_id == user.id)
)
if not facture:
raise HTTPException(status_code=404)
return factureUUID v4 prévient l'IDOR basé sur l'énumération. Les vérifications d'autorisation restent obligatoires — un lien de document partagé donne à n'importe quel destinataire un UUID valide qu'il peut utiliser pour accéder à la ressource si l'autorisation est absente.
Centraliser les vérifications de propriété prévient l'inconsistance développeur qui a causé CVE-2026-29056 dans Kanboard — où un chemin de code (inscription) manquait la vérification de propriété qu'un autre (paramètres) avait correctement implémentée :
// Middleware réutilisable — autorisation imposée avant l'exécution du handler
const exigerPropriete = (Modele, paramId = 'id') => async (req, res, next) => {
const ressource = await Modele.findByPk(req.params[paramId]);
if (!ressource) return res.status(404).json({ erreur: 'Non trouvé' });
if (ressource.userId !== req.user.id) {
return res.status(404).json({ erreur: 'Non trouvé' }); // pas 403
}
req.ressource = ressource;
next();
};
// Appliqué de façon cohérente sur toutes les routes de ressources
router.get('/api/documents/:id', authentifier, exigerPropriete(Document), handler);
router.put('/api/documents/:id', authentifier, exigerPropriete(Document), handler);
router.delete('/api/documents/:id', authentifier, exigerPropriete(Document), handler);Un IDOR de référence directe survient quand une application expose les clés primaires internes de la base de données — typiquement des entiers séquentiels — dans les URLs, paramètres de requête ou champs du corps. Un attaquant incrémente ou décrémente l'ID pour accéder aux enregistrements appartenant à d'autres utilisateurs.
L'attaquant s'authentifie comme utilisateur légitime, note un identifiant numérique dans une réponse (ex. order_id=1045), puis le remplace par des valeurs adjacentes (1044, 1046, 1000, 2000) dans les requêtes suivantes en utilisant son propre token de session. Si le serveur retourne l'enregistrement d'un autre utilisateur, l'endpoint est vulnérable.
CVE-2025-13526 dans le plugin WordPress OneClick Chat to Order (versions <= 1.0.8) permettait à des utilisateurs non authentifiés de récupérer les détails complets de n'importe quelle commande — noms de clients, adresses email, numéros de téléphone, adresses de facturation/livraison et métadonnées de paiement — en manipulant le paramètre order_id.
BreachVex teste les endpoints portant des IDs, sondant chacun à des valeurs delta telles que ±1, ±2, ±5, ±10, ±100 et ±1000 par rapport à l'ID de référence. La détection confirme via echo d'ID dans le corps de réponse, divergence de PII, ou différentiel de code de statut (403→200).
Toujours retourner 404. Retourner 403 confirme l'existence de la ressource, créant un oracle d'énumération qui indique aux attaquants exactement quels IDs sont valides. Un 404 constant force les attaquants à distinguer 'n'existe pas' de 'existe mais interdit' sans signal déterministe.
L'IDOR direct et BOLA décrivent la même vulnérabilité dans des contextes différents. IDOR est le terme web général ; BOLA (Broken Object Level Authorization) est le terme API-spécifique utilisé par l'OWASP API Security Top 10 (API1:2023). Les deux impliquent l'accès à un objet via un ID contrôlé par l'utilisateur sans validation de propriété.
Oui. Certaines API consomment des IDs utilisateur depuis des en-têtes personnalisés tels que X-User-ID, X-Account-ID ou X-Customer-ID. Ceux-ci sont moins souvent testés et manquent fréquemment de middleware d'autorisation. Tester toujours les paramètres d'ID basés sur des en-têtes en plus des paramètres d'URL et de corps.
La violation Optus (septembre 2022) a exposé 9,8 millions d'enregistrements clients via un endpoint REST retournant des données client indexées par des entiers séquentiels sans authentification. Les attaquants ont itéré GET /api/customers/1 jusqu'à /api/customers/9800000. C'est la démonstration canonique en conditions réelles d'IDOR direct à grande échelle.