Un utilisateur accède aux données d'un autre utilisateur au même niveau de privilège en modifiant un identifiant de ressource dans la requête.
TL;DR
L'escalade horizontale de privilèges est le sous-type IDOR le plus répandu. L'utilisateur A et l'utilisateur B ont le même rôle — tous deux sont des utilisateurs normaux, des clients payants, des membres inscrits. L'utilisateur A accède aux ressources privées de B en substituant l'identifiant de ressource de B dans une requête authentifiée comme A. Le serveur valide correctement la session de A — la vérification d'authentification réussit — mais n'effectue pas de second contrôle pour confirmer que l'identité de A est autorisée à accéder à la ressource spécifique que B possède.
Ce sous-type représente la majorité des rapports bug bounty IDOR de haute sévérité. L'OWASP API Security Top 10 le désigne comme API1:2023 — Broken Object Level Authorization (BOLA), une position qu'il occupe depuis l'édition inaugurale 2019 de la liste. Le projet OWASP API Security estime que BOLA est présent dans environ 40 % de toutes les attaques API.
La distinction critique avec l'escalade verticale : aucun gain de privilège ne se produit. L'utilisateur A ne devient pas admin. L'utilisateur A lit, modifie ou détruit simplement des données appartenant à B. La sévérité émerge de ce que contiennent ces données — dossiers médicaux, transactions financières, messages privés, documents propriétaires — et du fait qu'un seul endpoint vulnérable peut être exploité sur chaque paire d'utilisateurs de l'application.
La méthodologie de confirmation de référence utilise deux sessions indépendantes :
Configuration :
token_A = authentifier(utilisateur_A) # Session Utilisateur A
token_B = authentifier(utilisateur_B) # Session Utilisateur B
resource_id = POST /api/documents # créer ressource comme Utilisateur B
avec token_B → extraire id # noter l'ID retourné
Test :
GET /api/documents/{resource_id}
Authorization: Bearer {token_A} # requête comme Utilisateur A
Confirmer :
statut == 200 ET corps contient données de utilisateur_B → BOLA CONFIRMÉ
statut == 403 OU 404 → protégéLe même pattern s'applique aux opérations d'écriture et de suppression. Un IDOR en lecture (CVSS 6,5) qui s'escalade en écriture (modifier les données de B) ou suppression (détruire les données de B) atteint CVSS 7,5–8,8 :
# Lecture — IDOR confirmé
GET /api/messages/1338 HTTP/1.1
Authorization: Bearer <token_A>
# Escalade en écriture — tester PATCH/PUT avec l'ID de ressource de l'Utilisateur B
PATCH /api/messages/1338 HTTP/1.1
Authorization: Bearer <token_A>
Content-Type: application/json
{"contenu": "contenu contrôlé par l'attaquant"}
# Escalade en suppression — tester DELETE avec l'ID de ressource de l'Utilisateur B
DELETE /api/messages/1338 HTTP/1.1
Authorization: Bearer <token_A>| Variante | Technique | Impact |
|---|---|---|
| Accès GET entre pairs | Substituer l'ID de ressource du pair avec sa propre session | Lecture de données (PII, financier, médical) |
| IDOR en écriture entre pairs | PUT/PATCH avec l'ID de ressource du pair | Modification de données / intégrité |
| IDOR en suppression entre pairs | DELETE avec l'ID de ressource du pair | Destruction de données |
| IDOR en masse (batch) | Tableau d'IDs utilisateurs mixtes dans un seul corps | Exfiltration massive, contournement limitation de débit |
| IDOR GraphQL node | query { node(id: "VXNlcjoxMzM4") { ... } } | Tous les champs sur n'importe quel type d'objet |
| IDOR mutation GraphQL | Mutation avec l'ID d'objet du pair | Écriture/suppression sur n'importe quelle mutation |
Les endpoints de récupération en masse qui acceptent des tableaux d'IDs exposent un pattern à impact particulièrement élevé. Un seul appel HTTP peut exfiltrer des milliers d'enregistrements tout en ne consommant qu'une requête vers les limites de débit :
POST /api/documents/recuperation-masse HTTP/1.1
Authorization: Bearer <token_A>
Content-Type: application/json
{"document_ids": [1001, 1002, 1003, 5000, 9999, 50000]}Si des documents appartenant aux utilisateurs B, C ou D sont inclus dans la réponse, il s'agit d'un IDOR horizontal massif. L'autorisation par élément doit être appliquée dans le handler batch, pas seulement au niveau endpoint.
Les implémentations GraphQL selon la spécification Relay exposent une interface globale node(id:) qui résout n'importe quel objet par son ID global. Les IDs globaux sont typiquement des paires type+ID encodées en Base64 :
# Décoder son propre ID : VXNlcjoxMzM3 = base64("User:1337")
# Incrémenter le composant numérique et ré-encoder : User:1338
query {
node(id: "VXNlcjoxMzM4") {
... on User {
email
numeroTelephone
donnéesPrivées
}
}
}Les resolvers de champs individuels doivent vérifier la propriété indépendamment — l'authentification au niveau gateway qui confirme l'existence d'une session ne confirme pas que la session possède l'objet demandé. Le rapport HackerOne #2122671 (12 500 $) a exploité exactement ce pattern sur la plateforme HackerOne elle-même, permettant la suppression des certifications de n'importe quel utilisateur via une mutation GraphQL.
CVE-2024-46528 — IDOR KubeSphere (CVSS 6,5, ÉLEVÉ, septembre 2024) — KubeSphere 3.x (jusqu'à v3.4.1) et 4.x (jusqu'à v4.1.1) accordaient des permissions excessives au GlobalRole intégré authenticated — le rôle automatiquement assigné à chaque utilisateur connecté. Des utilisateurs de plateforme à faible privilège pouvaient accéder aux API de surveillance au niveau cluster, aux listes complètes d'utilisateurs et aux ressources namespace à travers le cluster sans aucune élévation de rôle. Le vecteur CVSS CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N reflète l'accès réseau à faible privilège avec un impact de confidentialité élevé. Corrigé dans KubeSphere 4.1.3.
HackerOne #2122671 — Plateforme HackerOne (12 500 $, GraphQL) — Un chercheur authentifié a trouvé une mutation GraphQL qui supprimait les certifications et licences professionnelles de n'importe quel utilisateur en soumettant l'id de certification de l'utilisateur cible. L'identité de l'appelant était validée (authentification réussie) mais non comparée au propriétaire de la certification (autorisation absente). La plateforme HackerOne elle-même — qui gère des programmes bug bounty — était vulnérable à l'IDOR horizontal via sa propre API GraphQL.
McDonald's McHire (juillet 2025, 64 millions d'enregistrements) — Le chercheur Ian Carroll a découvert que le paramètre lead_id sur /api/lead/cem-xhr était un entier séquentiel. Tout candidat inscrit pouvait accéder au transcript de chat de n'importe quel autre candidat — nom, numéro de téléphone, email, préférences de poste et résultats d'évaluation de personnalité — en décrémentant ou incrémentant le lead_id. Un seul appel API avec un ID manipulé retournait l'enregistrement complet d'entretien de recrutement d'un inconnu.
CVE-2025-1270 — Plateforme Anapi h6web (ÉLEVÉ, 2025) — Des utilisateurs authentifiés pouvaient modifier le paramètre pkrelated pour accéder aux enregistrements d'autres utilisateurs. Les actions suivantes étaient exécutées avec les privilèges de l'utilisateur usurpé, chaînant l'IDOR horizontal en escalade verticale — mouvement latéral via manipulation de paramètre.
GET /api/ressource/{id_ressource_b} avec Authorization: Bearer {token_A}.node(id:).# Autorize (extension Burp Suite) — replay automatique inter-sessions
# 1. Définir le cookie de session de l'Utilisateur B comme "session à faible privilège" dans Autorize
# 2. Naviguer comme Utilisateur A — Autorize rejoue chaque requête avec le cookie de B
# 3. Comparer codes de réponse et similarité de corps
# 4. Rouge = probablement vulnérable, vert = probablement protégé
# nuclei — templates IDOR
nuclei -u https://cible.com -t exposures/ids/ -H "Authorization: Bearer $TOKEN_A"BreachVex détecte l'IDOR horizontal en rejouant deux identités authentifiées l'une contre l'autre : quand une session primaire et une session secondaire sont disponibles, la session de l'Utilisateur B est testée contre les endpoints de l'Utilisateur A. La comparaison sémantique JSON extrait les champs d'identité (id, user_id, email, username, account_id, owner, ownerId). Le chevauchement de données entre sessions déclenche un finding BOLA. Une sonde PUT/PATCH de suivi escalade un BOLA en lecture confirmé en BFLA (accès en écriture) de sévérité CRITIQUE.
BFLA (Broken Function Level Authorization) est la variante à accès en écriture de l'IDOR horizontal. Si un attaquant peut utiliser le token de l'utilisateur A pour exécuter PATCH ou DELETE sur la ressource de l'utilisateur B, le finding s'escalade de ÉLEVÉ à CRITIQUE — l'intégrité des données et du compte sont toutes deux compromises. Tester toujours toutes les méthodes HTTP, pas seulement GET.
La défense structurelle : la clause de propriété fait partie de la requête de base de données, pas une vérification conditionnelle qui peut être ignorée :
# Django — VULNÉRABLE
def obtenir_document(request, doc_id):
doc = Document.objects.get(id=doc_id) # document de n'importe quel utilisateur
return JsonResponse(doc.to_dict())
# Django — SÉCURISÉ
from django.shortcuts import get_object_or_404
def obtenir_document(request, doc_id):
doc = get_object_or_404(Document, id=doc_id, owner=request.user)
return JsonResponse(doc.to_dict())
# get_object_or_404 retourne 404 pour "non trouvé" ET "mauvais propriétaire"
# Cela prévient les oracles d'énumérationLes vérifications de rôle (RBAC) seules sont insuffisantes pour l'IDOR horizontal — les deux utilisateurs passent la vérification de rôle. La propriété au niveau objet doit être une barrière séparée et obligatoire :
// Express — middleware d'autorisation à double couche
const authentifier = require('./middleware/authentifier'); // Couche 1 : identité
const exigerPropriete = (Modele, paramId) => async (req, res, next) => { // Couche 2 : propriété
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é à chaque route gérant des ressources appartenant à un utilisateur
router.get('/api/messages/:id', authentifier, exigerPropriete(Message, 'id'), getHandler);
router.patch('/api/messages/:id', authentifier, exigerPropriete(Message, 'id'), patchHandler);
router.delete('/api/messages/:id', authentifier, exigerPropriete(Message, 'id'), deleteHandler);L'autorisation en GraphQL doit se produire au niveau du resolver, pas de la gateway. Chaque resolver recevant un ID d'objet doit vérifier la propriété indépendamment :
// Resolver GraphQL — VULNÉRABLE (auth gateway uniquement)
const resolvers = {
Query: {
document: (_, { id }, { user }) => Document.findById(id), // aucune vérification de propriété
}
};
// Resolver GraphQL — SÉCURISÉ (propriété au niveau resolver)
const resolvers = {
Query: {
document: async (_, { id }, { user }) => {
const doc = await Document.findById(id);
if (!doc) return null;
if (doc.ownerId !== user.id) return null; // traiter comme non trouvé
return doc;
}
}
};L'escalade horizontale de privilèges survient quand un utilisateur accède aux ressources appartenant à un autre utilisateur au même niveau de privilège — l'utilisateur A lit, modifie ou supprime les enregistrements de l'utilisateur B en substituant l'identifiant de B dans une requête authentifiée. Les deux utilisateurs ont le même rôle ; la vulnérabilité est une validation de propriété au niveau objet manquante.
L'escalade horizontale est latérale : l'utilisateur A accède aux données de l'utilisateur B sans gagner de privilèges élevés. L'escalade verticale est ascendante : un utilisateur normal accède aux fonctions ou données admin. L'horizontale est typiquement CVSS 6,5–7,5 ; la verticale atteint 8,8–9,1 car elle implique une compromission à l'échelle du système.
BOLA (Broken Object Level Authorization) est le terme API-spécifique pour la même vulnérabilité, défini par OWASP API Security API1:2023. L'IDOR horizontal dans un contexte API REST ou GraphQL est classifié comme BOLA. Le correctif est identique : validation de propriété sur chaque opération API au niveau objet.
La méthodologie à deux tokens crée deux comptes utilisateurs distincts, authentifie les deux, crée des ressources avec l'un, puis teste l'accès à ces ressources avec le token de session de l'autre utilisateur. Si le token du second utilisateur récupère la ressource privée du premier, l'IDOR horizontal est confirmé.
Les resolvers GraphQL traitent les requêtes de champs individuels. L'authentification se produit typiquement au niveau de la gateway. Si un resolver récupérant des données utilisateur valide l'existence d'un token de session mais pas qu'il correspond au propriétaire de l'objet demandé, n'importe quel utilisateur authentifié peut interroger n'importe quel ID d'objet. L'interface node(id:) dans les schémas de style Relay est une surface d'attaque commune.
Le rapport HackerOne #2122671 a découvert qu'une mutation GraphQL sur la plateforme HackerOne elle-même permettait à tout utilisateur authentifié de supprimer les certifications et licences de n'importe quel autre utilisateur. L'ID d'objet de la mutation n'était pas validé contre l'identité de l'appelant. Prime : 12 500 $.
CVE-2024-46528 dans KubeSphere 3.x et 4.x (CVSS 6,5) accordait des permissions excessives au GlobalRole 'authenticated'. Tout utilisateur authentifié à faible privilège pouvait accéder aux données de surveillance au niveau cluster, aux listes complètes d'utilisateurs et aux ressources namespace appartenant à d'autres utilisateurs — toutes des opérations réservées au cluster-admin.
Oui. Les endpoints acceptant des tableaux d'IDs dans une seule requête peuvent exfiltrer des milliers d'enregistrements en un seul appel HTTP, contournant la limitation de débit. Ce pattern est courant dans les conceptions d'API d'export en masse ou de récupération par lots et est une variante distincte de haute sévérité nécessitant une autorisation explicite par élément.