Lesson 6/14 14 min

Broken access control

OWASP's number one: reading someone else's invoice by changing an id in the URL (IDOR), reaching admin pages without the right. Attack it yourself, then the golden rule: check authorization on the server, on every request.

La facture du voisin, à un chiffre près

Les leçons précédentes portaient sur ce que l'attaquant injecte dans le système : du SQL, du HTML, une commande shell, des données travesties en code. Ici la menace change de nature : l'attaquant n'envoie rien d'illicite, il demande simplement une ressource que le serveur lui remet sans jamais vérifier à qui elle appartient.

Vous êtes connecté à votre espace client. Vous cliquez sur « Ma facture » et l'URL affiche :

moncompte.fr/facture?id=4087

Votre facture s'affiche, normal : 4087, c'est votre numéro de client. Par curiosité, vous remplacez 4087 par 4086 dans la barre d'adresse. Et là, le site vous affiche sans broncher la facture de quelqu'un d'autre : son nom, son montant, son adresse. Vous essayez 4001 : c'est la facture confidentielle de la direction.

Vous n'avez rien piraté. Vous êtes resté connecté à votre propre compte, vous avez juste changé un numéro dans l'URL. Et le serveur vous a montré la facture sans jamais vérifier qu'elle vous appartenait :

// /facture.php : l'utilisateur est bien connecté
$id = $_GET['id'];
$facture = $db->query("SELECT * FROM factures WHERE id = $id")->fetch();
echo render($facture); // affiche la facture demandée… sans vérifier à qui elle appartient

Ça, c'est le contrôle d'accès défaillant (Broken Access Control). Sa forme la plus courante a un nom : l'IDOR (Insecure Direct Object Reference), accéder à l'objet d'un autre en changeant son identifiant. C'est le numéro 1 de l'OWASP 2025 (A01), la faille la plus répandue du web. La raison est simple : chaque appli a des données qui appartiennent à un utilisateur précis. Il suffit d'oublier d'en protéger une, et elle fuite.

Pourquoi ça marche, et les formes que ça prend

La cause tient en une confusion : l'application a mélangé authentification et autorisation. L'authentification répond à « qui êtes-vous ? » (vous êtes connecté). L'autorisation répond à « avez-vous le droit d'accéder à cette ressource, de faire cette action ? ». Être connecté ne veut pas dire avoir le droit de tout voir. Ici, le serveur a reçu id=4086 et a obéi, sans jamais se demander si cet utilisateur avait le droit de voir cette facture.

Le contrôle d'accès défaillant prend plusieurs formes :

  • IDOR / accès horizontal : lire ou modifier la ressource d'un autre utilisateur du même niveau (la facture du voisin, en changeant l'id).
  • Élévation verticale : un utilisateur normal atteint des fonctions réservées à un rôle supérieur (la page /admin, l'endpoint qui supprime un compte).
  • Forced browsing : une page non affichée dans le menu mais accessible en tapant l'URL (/admin/export). Cacher le lien ne le protège pas.
  • Trafiquer la requête : ajouter ?role=admin, faire passer un champ caché is_admin de 0 à 1, changer la méthode HTTP.

Le fil rouge est toujours le même : la vérification du droit est absente, ou faite au mauvais endroit (côté client, ou en faisant confiance à une donnée envoyée par le client).

Le faux remède : « j'ai mis un identifiant imprévisible (UUID) » ou « j'ai caché le bouton admin pour les non-admins » ne protège pas. L'obscurité n'est pas un contrôle d'accès. L'attaquant appelle l'endpoint directement, et un UUID finit toujours par fuiter : logs, en-tête Referer, lien partagé. La vraie parade est de vérifier le droit côté serveur, à chaque requête (section 4).

À vous d'attaquer : lisez la facture d'un autre

Voici un espace client vraiment vulnérable, simulé dans votre navigateur (rien n'est envoyé, aucun risque). Vous êtes connecté en tant que client #4087 et votre facture s'affiche. Le serveur renvoie la facture demandée sans vérifier qu'elle vous appartient. Votre mission : afficher une facture qui n'est pas la vôtre. Bonus : remontez jusqu'à la facture #4001.

Espace client · mes factures · connecté : client #4087
moncompte.fr/facture?id=
Bloqué ? Voir la solution (et de quoi explorer)

Le numéro dans l'URL, id=4087, c'est vous. Le serveur ne vérifie pas à qui appartient la facture : il suffit donc de demander un autre numéro. Essayez :

4086, 4088, 4090 (d'autres clients), puis 4001 (la direction).

Chaque numéro voisin vous donne la facture de quelqu'un d'autre. Dans une vraie attaque, on ne le fait pas à la main : on script l'énumération (4000, 4001, 4002…) et on aspire toutes les factures d'un coup. C'est comme ça que beaucoup de fuites de données massives arrivent.

Le correctif : vérifier le droit côté serveur, à chaque requête

La règle est simple à énoncer : chaque requête qui touche une ressource ou déclenche une action sensible doit vérifier, sur le serveur, que l'utilisateur connecté en a le droit. Deux vérifications, selon le cas.

Le droit horizontal (la ressource est-elle à moi ?). On lie la requête à l'utilisateur de la session, jamais à un identifiant venu de la requête :

// SÛR : on filtre sur l'id ET sur l'utilisateur de la SESSION
$stmt = $db->prepare("SELECT * FROM factures WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $_SESSION['user_id']]);
$facture = $stmt->fetch();
if (!$facture) { http_response_code(404); exit; } // pas à vous = ça n'existe pas

Le droit vertical (ai-je le rôle ?). On vérifie le rôle sur l'endpoint lui-même, pas seulement en cachant le bouton dans l'interface :

// SÛR : le contrôle est sur CHAQUE endpoint sensible
if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; }
deleteUser($id);

En Go, même logique : l'identité vient du contexte d'authentification (session ou jeton déjà vérifié), et on compare le propriétaire de la ressource :

// l'identité vient de l'auth vérifiée, pas d'un paramètre de la requête
user := auth.FromContext(r)
inv, err := db.Invoice(id)
if err != nil || inv.UserID != user.ID {
    http.Error(w, "Not found", http.StatusNotFound) // 404, on ne révèle rien
    return
}

Le piège classique : « corriger » l'IDOR avec WHERE id = ? AND user_id = ? mais en prenant user_id dans la requête ($_GET['user_id'], un champ caché du formulaire). C'est inutile : l'attaquant contrôle la requête, il met le user_id qu'il veut. L'identité doit toujours venir de la session (posée à la connexion, après vérification du mot de passe), jamais d'un paramètre que l'utilisateur peut changer.

Défense en profondeur :

  1. vérifier l'autorisation côté serveur sur chaque requête, à partir de la session ;
  2. refuser par défaut (deny by default) : 403/404 tant que l'accès n'est pas explicitement autorisé ;
  3. centraliser la logique (un middleware, une policy) pour ne l'oublier sur aucun endpoint ;
  4. renvoyer 404 plutôt que 403 sur la ressource d'un autre, pour ne même pas révéler qu'elle existe ;
  5. logger les refus d'accès et limiter le débit, pour repérer une énumération d'identifiants.

Référence : OWASP Authorization Cheat Sheet.

On vient de voir comment corriger la faille côté défense. Voyons maintenant comment un pentester la trouverait dans votre application avant qu'un attaquant le fasse.

La méthode et l'arsenal du pentester

1. Cartographier. Lister tous les endroits où un paramètre désigne un objet (id, uuid, compte, fichier) ou une action sensible (supprimer, exporter, changer un rôle). Chacun est un point de contrôle d'accès à tester.

2. Tester l'horizontal (IDOR). On se crée deux comptes, A et B. Connecté en A, on essaie d'atteindre une ressource de B en changeant l'id. On incrémente, on décrémente, on énumère les identifiants séquentiels. Si la ressource de B s'affiche, c'est gagné.

3. Tester le vertical. Avec un compte normal, on appelle directement les endpoints admin (forced browsing : /admin, /api/admin/users), on rejoue une requête d'admin sans avoir le rôle, on trafique un champ role ou is_admin.

4. Tester le tampering. Changer la méthode HTTP (un GET protégé mais pas le POST équivalent), ajouter des paramètres (?admin=true, ?debug=1), modifier les champs cachés d'un formulaire.

5. L'arsenal.

  • Burp Suite : le roi ici. Repeater pour changer un id et rejouer, Intruder pour énumérer les identifiants séquentiels, et surtout l'extension Autorize : on lui donne le cookie d'un compte à faibles privilèges, il rejoue chaque requête avec ce cookie et signale en rouge celles qui passent alors qu'elles ne devraient pas. L'outil d'autorisation par excellence.
  • ffuf / dirsearch : pour le forced browsing, découvrir les endpoints admin non liés dans l'interface.
  • Le cerveau, surtout. Le contrôle d'accès est une faille de logique : un scanner automatique ne sait pas qui « devrait » voir quoi. C'est la faille la plus manuelle à chasser, et l'une des plus rentables.

L'impact, et pourquoi c'est le numéro 1

Lire une facture voisine paraît anecdotique. Voici ce que ça devient à grande échelle.

1. La fuite de données massive. Un IDOR sur un ?id= séquentiel ne se fait pas à l'unité. On écrit un script de dix lignes qui demande 4000, 4001, 4002… et on aspire tout : toutes les factures, tous les dossiers, toutes les données personnelles. Beaucoup de fuites retentissantes sont exactement ça : un identifiant qu'il suffisait d'incrémenter (souvent sur une API mobile).

2. La prise de l'application. Une élévation verticale qui atteint l'espace admin, c'est la main sur tout : créer ou supprimer des comptes, changer les rôles, exporter la base, modifier les prix. La faille d'un seul endpoint oublié donne le contrôle complet.

3. Pourquoi le #1 de l'OWASP. Parce que c'est partout : dès qu'une application a des données par utilisateur (et elles en ont toutes), elle doit vérifier le droit à chaque accès. Il suffit d'en oublier un. Et c'est souvent trivial à exploiter : pas d'outil exotique, juste un id qu'on change.

Ce que ça révèle côté défense : on ne « rattrape » pas un contrôle d'accès par un détail cosmétique (cacher un bouton, brouiller une URL). L'empilement compte : autorisation serveur systématique (la base), deny by default, 404 sur une ressource étrangère (ne pas confirmer son existence), et logs + rate limiting pour repérer une énumération en cours. Une seule couche ne suffit jamais (leçon 2).

Cadre légal, encore : changer un id pour voir les données d'un autre, même « juste pour voir », c'est un accès non autorisé, et c'est un délit. On ne teste que ses propres systèmes ou une cible explicitement autorisée (leçon 1).

Le carnet de tests du contrôle d'accès

Les réflexes à dérouler sur vos apps ou une cible autorisée. Pas des « payloads » au sens injection : ici, on manipule des identifiants et des droits.

IDOR (accès horizontal) :

moncompte.fr/facture?id=4087   →   ?id=4086   ?id=4001
/api/users/123   →   /api/users/124
/api/orders/8f2a...   →   l'uuid d'un autre compte (s'il fuite)

Forced browsing (accès vertical) :

/admin
/admin/export
/api/admin/users
/internal/debug

Trafiquer la requête :

?role=admin        ?debug=1        champ caché is_admin=0 → 1
GET protégé ?  Réessayer en POST / PUT / DELETE

Les listes et outils. PayloadsAllTheThings (IDOR) et le chapitre IDOR de HackTricks recensent les variantes et contournements. Pour s'entraîner légalement : les labs « Access control » de la Web Security Academy (gratuits, guidés) et OWASP Juice Shop.

Rappel. Ce carnet sert à tester/auditer, pas à se défendre : aucune de ces astuces ne marche si le serveur vérifie le droit à chaque requête, à partir de la session. Et on ne teste que ses propres systèmes ou des plateformes autorisées (leçon 1).

Savoir qu'une ressource est protégée suppose d'abord de savoir qui est l'utilisateur qui la demande. C'est précisément le travail de l'authentification : la leçon 7 explique pourquoi certains hashages de mots de passe sont dangereux, et comment ralentir un attaquant qui tente de les craquer.

Your neighbor's invoice, one digit away

The previous lessons were about what an attacker injects into the system: SQL, HTML, a shell command, data masquerading as code. Here the nature of the threat shifts: the attacker sends nothing illicit, they simply request a resource that the server hands over without ever checking who it belongs to.

You're logged into your customer area. You click "My invoice" and the URL shows:

myaccount.com/invoice?id=4087

Your invoice appears, fine: 4087 is your customer number. Out of curiosity, you replace 4087 with 4086 in the address bar. And there, the site cheerfully shows you someone else's invoice: their name, their amount, their address. You try 4001: it's the confidential management invoice.

You hacked nothing. You stayed logged into your own account, you just changed a number in the URL. And the server showed you the invoice without ever checking it belonged to you:

// /invoice.php — the user is properly logged in
$id = $_GET['id'];
$invoice = $db->query("SELECT * FROM invoices WHERE id = $id")->fetch();
echo render($invoice); // shows the requested invoice… without checking who it belongs to

That's broken access control. Its most common shape has a name: IDOR (Insecure Direct Object Reference), reaching someone else's object by changing its identifier. It's OWASP's number one in 2025 (A01), the most widespread web flaw. The reason is simple: every app has data that belongs to a specific user. Forget to guard just one resource, and it leaks.

Why it works, and the shapes it takes

The cause is a confusion: the app mixed up authentication and authorization. Authentication answers "who are you?" (you're logged in). Authorization answers "are you allowed to access this resource, to do this action?". Being logged in doesn't mean you may see everything. Here, the server received id=4086 and obeyed, without ever asking whether this user was allowed to see this invoice.

Broken access control takes several shapes:

  • IDOR / horizontal access: reading or modifying another same-level user's resource (the neighbor's invoice, by changing the id).
  • Vertical escalation: a normal user reaches functions reserved for a higher role (the /admin page, the endpoint that deletes an account).
  • Forced browsing: a page not shown in the menu but reachable by typing the URL (/admin/export). Hiding the link doesn't protect it.
  • Tampering with the request: adding ?role=admin, flipping a hidden is_admin field from 0 to 1, changing the HTTP method.

The common thread is always the same: the authorization check is missing, or done in the wrong place (client-side, or trusting data the client sent).

Beware the false cure: "I used an unguessable identifier (UUID)" or "I hid the admin button from non-admins" doesn't protect. Obscurity isn't access control. The attacker calls the endpoint directly, and a UUID always leaks eventually: logs, the Referer header, shared links. The real fix is to check authorization on the server, on every request (section 4).

Your turn to attack: read someone else's invoice

Here's a genuinely vulnerable customer area, simulated in your browser (nothing is sent, no risk). You're logged in as customer #4087 and your invoice is shown. The server returns the requested invoice without checking it belongs to you. Your mission: display an invoice that isn't yours. Bonus: reach invoice #4001.

Customer area · my invoices · logged in: customer #4087
myaccount.com/invoice?id=
Stuck? Show the solution (and how to explore)

The number in the URL, id=4087, is you. The server doesn't check who the invoice belongs to, so just ask for another number. Try:

4086, 4088, 4090 (other customers), then 4001 (management).

Every nearby number gives you someone else's invoice. In a real attack, you don't do it by hand: you script the enumeration (4000, 4001, 4002…) and vacuum up every invoice at once. That's how many massive data breaches happen.

The fix: check authorization on the server, on every request

The rule is simple to state: every request that touches a resource or triggers a sensitive action must verify, on the server, that the logged-in user is allowed. Two checks, depending on the case.

Horizontal right (is the resource mine?). You tie the request to the session user, never to an identifier coming from the request:

// SAFE: filter on the id AND on the SESSION user
$stmt = $db->prepare("SELECT * FROM invoices WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $_SESSION['user_id']]);
$invoice = $stmt->fetch();
if (!$invoice) { http_response_code(404); exit; } // not yours = doesn't exist

Vertical right (do I have the role?). You check the role on the endpoint itself, not just by hiding the button in the UI:

// SAFE: the check is on EVERY sensitive endpoint
if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); exit; }
deleteUser($id);

In Go, same logic: identity comes from the auth context (an already-verified session or token), and you compare the resource's owner:

// identity comes from verified auth, not from a request parameter
user := auth.FromContext(r)
inv, err := db.Invoice(id)
if err != nil || inv.UserID != user.ID {
    http.Error(w, "Not found", http.StatusNotFound) // 404, reveal nothing
    return
}

The classic trap: "fixing" the IDOR with WHERE id = ? AND user_id = ? but taking user_id from the request ($_GET['user_id'], a hidden form field). It's useless: the attacker controls the request, so they set whatever user_id they want. Identity must always come from the session (set at login, after the password check), never from a parameter the user can change.

Defense in depth:

  1. check authorization on the server for every request, from the session;
  2. deny by default: 403/404 until access is explicitly allowed;
  3. centralize the logic (a middleware, a policy) so no endpoint is forgotten;
  4. return 404 rather than 403 on someone else's resource, so you don't even reveal it exists;
  5. log access denials and rate-limit, to catch an identifier enumeration.

Reference: OWASP Authorization Cheat Sheet.

Now that you know how to fix the flaw on the defense side, let's see how a pentester would find it in your application before an attacker does.

The pentester's method and arsenal

1. Map it out. List every place a parameter names an object (id, uuid, account, file) or a sensitive action (delete, export, change a role). Each is an access-control checkpoint to test.

2. Test horizontal (IDOR). Create two accounts, A and B. Logged in as A, try to reach a resource of B by changing the id. Increment, decrement, enumerate sequential identifiers. If B's resource shows up, you win.

3. Test vertical. With a normal account, call admin endpoints directly (forced browsing: /admin, /api/admin/users), replay an admin request without the role, tamper with a role or is_admin field.

4. Test tampering. Change the HTTP method (a protected GET but not the matching POST), add parameters (?admin=true, ?debug=1), modify a form's hidden fields.

5. The arsenal.

  • Burp Suite: the king here. Repeater to change an id and replay, Intruder to enumerate sequential identifiers, and above all the Autorize extension: give it a low-privilege account's cookie, it replays every request with that cookie and flags in red the ones that go through when they shouldn't. The authorization tool par excellence.
  • ffuf / dirsearch: for forced browsing, to discover admin endpoints not linked in the UI.
  • The brain, above all. Access control is a logic flaw: an automated scanner doesn't know who "should" see what. It's the most manual flaw to hunt, and one of the most rewarding.

The impact, and why it's number one

Reading a neighbor's invoice sounds trivial. Here's what it becomes at scale.

1. The massive data breach. An IDOR on a sequential ?id= isn't done one at a time. You write a ten-line script that requests 4000, 4001, 4002… and vacuum up everything: every invoice, every record, every piece of personal data. Many high-profile leaks are exactly that: an identifier you only had to increment (often on a mobile API).

2. The application takeover. A vertical escalation that reaches the admin area means a hand on everything: create or delete accounts, change roles, export the database, edit prices. The flaw of a single forgotten endpoint gives full control.

3. Why OWASP's #1. Because it's everywhere: as soon as an app has per-user data (and they all do), it must check the right on every access. Forgetting just one is enough. And it's often trivial to exploit: no exotic tool, just an id you change.

What this reveals for defense: you don't "patch up" access control with a cosmetic detail (hiding a button, scrambling a URL). The stack matters: systematic server-side authorization (the base), deny by default, 404 on someone else's resource (don't confirm it exists), and logs + rate limiting to catch an enumeration in progress. One layer is never enough (lesson 2).

Legal frame, again: changing an id to view someone else's data, even "just to see", is unauthorized access, and it's a crime. Only test your own systems or an explicitly authorized target (lesson 1).

The access-control test notebook

The reflexes to run on your apps or an authorized target. Not "payloads" in the injection sense: here you manipulate identifiers and rights.

IDOR (horizontal access):

myaccount.com/invoice?id=4087   →   ?id=4086   ?id=4001
/api/users/123   →   /api/users/124
/api/orders/8f2a...   →   another account's uuid (if it leaks)

Forced browsing (vertical access):

/admin
/admin/export
/api/admin/users
/internal/debug

Tampering with the request:

?role=admin        ?debug=1        hidden field is_admin=0 → 1
GET protected ?  Retry as POST / PUT / DELETE

The lists and tools. PayloadsAllTheThings (IDOR) and the HackTricks IDOR chapter list the variants and bypasses. To practice legally: the "Access control" labs of the Web Security Academy (free, guided) and OWASP Juice Shop.

Reminder. This notebook is for testing/auditing, not defending: none of these tricks work if the server checks the right on every request, from the session. And only test your own systems or authorized platforms (lesson 1).

Knowing that a resource is protected first requires knowing who is asking for it. That's exactly what authentication handles: lesson 7 explains why some password-hashing schemes are dangerous, and how to slow down an attacker trying to crack them.

Prédisez avant de lire

Un dev écrit SELECT * FROM factures WHERE id = ? (en liant seulement l'id de l'URL). Un autre écrit SELECT * FROM factures WHERE id = ? AND user_id = ? (en liant l'id ET l'user_id de la session). Avant de dérouler : avec lequel un utilisateur connecté peut-il lire la facture d'un autre ?

Voir la réponse

Le premier est vulnérable : il renvoie la facture de l'id demandé, peu importe à qui elle est. N'importe quel utilisateur connecté change l'id et lit la facture d'un autre : c'est l'IDOR. Le second est sûr : il exige que la facture appartienne à l'utilisateur de la session. Si l'id n'est pas le vôtre, la requête ne renvoie rien (on répond alors 404). Point crucial : le user_id doit venir de la session, jamais d'un paramètre de la requête.

Predict before reading on

One dev writes SELECT * FROM invoices WHERE id = ? (binding only the URL's id). Another writes SELECT * FROM invoices WHERE id = ? AND user_id = ? (binding the id AND the session's user_id). Before you expand: with which one can a logged-in user read someone else's invoice?

Show the answer

The first is vulnerable: it returns the invoice for the requested id, no matter whose it is. Any logged-in user changes the id and reads someone else's invoice: that's IDOR. The second is safe: it requires the invoice to belong to the session user. If the id isn't yours, the query returns nothing (you then answer 404). Crucial point: user_id must come from the session, never from a request parameter.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Avec tes mots : quelle est la différence entre authentification et autorisation, et pourquoi le contrôle d'accès doit-il se faire côté serveur, à partir de la session ?

Une bonne explication dit : l'authentification prouve qui vous êtes (la connexion) ; l'autorisation décide ce que vous avez le droit de faire ou de voir. Être connecté ne donne pas accès à tout. Le contrôle doit se faire côté serveur. Pourquoi ? Parce que tout ce qui vient du navigateur (URL, champ caché, cookie de rôle, bouton masqué) est sous le contrôle de l'attaquant : il peut le changer. Il doit aussi partir de la session, posée à la connexion après vérification du mot de passe : c'est la seule identité que l'utilisateur ne peut pas falsifier. La question, à chaque requête : « cet utilisateur a-t-il le droit sur cette ressource ? ».
🧠 Rappel libre
Rappel libre

Sans remonter : explique la différence entre un IDOR et une élévation verticale, donne la règle d'or du contrôle d'accès, et dis pourquoi cacher le bouton admin ne protège pas.

IDOR (horizontal) : on accède à la ressource d'un autre utilisateur du même niveau en changeant son identifiant (la facture id=4086 au lieu de la sienne). Élévation verticale : on atteint des fonctions réservées à un rôle supérieur (l'espace admin, l'endpoint qui supprime un compte). Règle d'or : vérifier le droit côté serveur, à chaque requête, à partir de la session (jamais à partir d'un paramètre que le client peut changer). Cacher le bouton admin ne protège pas car c'est purement côté client : l'attaquant ne passe pas par le bouton, il appelle l'endpoint directement.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Tu signales à l'IA que n'importe quel client peut lire la facture d'un autre en changeant l'id. Elle répond : « Je remplace l'id séquentiel par un UUID aléatoire dans l'URL (/facture?id=8f2a-...). Comme il est impossible à deviner, plus personne ne peut tomber sur la facture d'un autre. » Tu acceptes, ou tu rejettes ?

À rejeter. C'est de la sécurité par l'obscurité : l'UUID rend la devinette plus dure, mais ne vérifie toujours rien. La faille reste entière. Un UUID fuite par mille canaux (historique, en-tête Referer vers un site tiers, logs, lien partagé, réponse d'une autre API), et il reste souvent énumérable ailleurs. Surtout, ça ne change pas le fond : un utilisateur qui obtient l'UUID d'un autre accède quand même à sa facture. Le vrai correctif est inchangé : vérifier côté serveur que la facture appartient à l'utilisateur de la session (WHERE id = ? AND user_id = ?, le user_id venant de la session). L'UUID peut s'ajouter en bonus, mais ne remplace jamais le contrôle d'accès.
En étant connecté, vous changez ?id=4087 en ?id=4086 et le site affiche la facture d'un autre client. Quelle est la faille ?
Quelle est la défense centrale contre le contrôle d'accès défaillant ?
Le bouton « Supprimer un utilisateur » est masqué pour les comptes normaux. Pourtant un utilisateur normal réussit l'action en envoyant la requête POST /admin/delete-user à la main. Pourquoi ?
Un dev « corrige » l'IDOR avec WHERE id = ? AND user_id = ?, mais en prenant user_id dans $_GET['user_id']. Pourquoi est-ce toujours cassé ?
Next step

You can force access to another user's data. Lesson 7 goes back to the front door: authentication. Why md5 is a mistake, how bcrypt and Argon2 protect passwords, and how to slow down brute force.

Lesson 7: Authentication & passwords →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement