L’élévation de privilèges horizontale et verticale
Dans un immeuble, un résident a la clé de son appartement et de la porte d’entrée ; le gardien a aussi celle des parties communes ; le syndic a accès à la chaufferie. Si un résident parvenait à ouvrir la porte de son voisin, ce serait un problème. S’il accédait à la chaufferie, ce serait un problème d’une autre nature. En sécurité web, ces deux situations portent des noms : l’élévation de privilèges horizontale et verticale.
Rappel légal
Obtenir des droits que l’on n’a pas, ou accéder à des fonctions d’administration sans y être autorisé, constitue un accès frauduleux (articles 323-1 à 323-3-1 du Code pénal). Toutes les expériences ci-dessous se font dans votre application d’entraînement (DVWA, OWASP Juice Shop, PortSwigger Academy), avec des comptes de test que vous avez créés ou qui sont prévus par le laboratoire.
Deux directions, deux risques
| Horizontale | Verticale | |
|---|---|---|
| Définition | Accéder aux ressources d’un utilisateur de même niveau | Accéder à des fonctions réservées à un niveau supérieur |
| Exemple | Un client consulte le profil d’un autre client | Un client accède à une page d’administration |
| Lien avec la leçon précédente | L’IDOR en est la forme classique | Absence de contrôle de rôle sur une fonction |
| Impact typique | Fuite ou modification de données d’autrui | Prise de contrôle de l’application ou de ses données |
Ces deux familles relèvent du même défaut racine : l’application accorde ou refuse l’accès à partir d’indications que l’utilisateur peut influencer, au lieu de vérifier systématiquement ses droits côté serveur.
L’élévation verticale : les fonctions « cachées »
Un schéma très courant : l’application masque les fonctions d’administration aux utilisateurs ordinaires (le lien n’apparaît pas dans leur menu), et en conclut qu’elles sont protégées. Mais masquer n’est pas protéger : une page réservée aux administrateurs reste accessible à quiconque connaît son adresse, si le serveur ne contrôle pas le rôle à chaque requête. C’est ce qu’on appelle la sécurité par l’obscurité : elle ne tient que tant que personne ne cherche.
Votre carte du chapitre 1 contient souvent des indices : une page /admin repérée dans un script, une interface d’API, un lien commenté dans le code source. Pour chacune, la question est : « que se passe-t-il si un utilisateur ordinaire demande directement cette adresse ? ».
Test d’accès direct
- Avec un compte administrateur de test, notez toutes les adresses et requêtes des fonctions réservées (gestion des utilisateurs, suppression, configuration).
- Avec un compte utilisateur ordinaire, rejouez chacune de ces requêtes depuis le module de rejeu, avec le cookie de l’utilisateur ordinaire.
- Pour chaque requête, notez la réponse : refus (bon), redirection, ou succès (constat).
- Faites de même sans aucune session (anonyme) : une fonction d’administration accessible à un visiteur non connecté est encore plus préoccupante.
Notez que ce test fonctionne parce qu’on dispose, dans l’application d’entraînement, de comptes aux niveaux différents. En audit réel, le client fournit généralement ces comptes de test dans le cadre de la mission.
Les rôles transmis par le client
Une autre erreur de conception : stocker le niveau de droits dans un élément que le client contrôle. Lors de votre cartographie, repérez des éléments comme :
- un cookie contenant un rôle lisible (
role=utilisateur) ; - un champ masqué ou un paramètre d’adresse (
admin=false) ; - un champ supplémentaire dans le corps d’une requête de profil, que le serveur accepte sans le filtrer.
Ce dernier cas porte un nom : l’affectation de masse (mass assignment). Quand l’application copie automatiquement tous les champs reçus dans un objet, un utilisateur peut ajouter un champ que l’interface n’avait pas prévu (comme un champ de rôle) et le voir pris en compte. Le principe de défense : le serveur ne doit accepter qu’une liste blanche de champs modifiables par l’utilisateur, et les droits ne doivent jamais dépendre de ce que le client envoie.
Contourner un contrôle par la méthode ou le chemin
Quelques subtilités à garder en tête lors d’un audit (au niveau des notions) :
- Le contrôle par méthode : une page protégée en
GETmais pas enPOST, ou l’inverse. - Le contrôle par adresse : une règle qui protège
/adminmais pas une variante d’écriture de la même adresse (majuscules, barre finale, encodage), selon le serveur. - Le contrôle sur une seule couche : l’interface vérifie les droits, mais l’API sous-jacente non.
- Les fonctions multi-étapes : un assistant en plusieurs écrans où seule la première étape est contrôlée.
La leçon à retenir est que l’autorisation doit être vérifiée à chaque requête, sur chaque fonction, côté serveur, y compris pour des étapes intermédiaires.
Défendre : un modèle d’autorisation clair
- Refus par défaut : toute fonction est interdite tant qu’un droit explicite ne l’autorise pas.
- Contrôle centralisé : un mécanisme commun, appliqué à toutes les routes, plutôt que des contrôles dispersés qu’on peut oublier.
- Droits issus de la session côté serveur, jamais d’un paramètre fourni par le client.
- Principe du moindre privilège : chaque rôle n’a que ce dont il a besoin.
- Liste blanche de champs modifiables lors des mises à jour.
- Journalisation des actions d’administration et des refus.
- Tests automatisés d’autorisation : une matrice rôle × fonction vérifiée à chaque livraison.
// Contrôle central appelé avant tout traitement
function exiger_role(string $role): void {
if (($_SESSION['role'] ?? '') !== $role) {
http_response_code(403);
exit('Accès interdit');
}
}
exiger_role('administrateur'); // en tête de chaque page ou route d'administration
Astuce
Construisez une matrice d’accès : en lignes, les fonctions de l’application ; en colonnes, les rôles (anonyme, utilisateur, administrateur). Dans chaque case, notez « autorisé » ou « interdit » d’après la spécification, puis testez chaque case. Les écarts sont vos constats.
À vous de jouer
- Dans votre application d’entraînement, dressez la liste de cinq fonctions (par exemple : voir son profil, modifier son profil, voir la liste des utilisateurs, supprimer un utilisateur, consulter la configuration).
- Pour chacune, indiquez le rôle qui devrait pouvoir l’utiliser.
- Remplissez la matrice « résultat observé » avec vos comptes de test (anonyme, utilisateur, administrateur).
- Pour chaque écart, rédigez un constat avec gravité et correction.
Voir une solution
Exemple de matrice : « supprimer un utilisateur » : attendu administrateur seulement ; observé : utilisateur ordinaire reçoit un refus, anonyme reçoit une redirection vers la connexion. Si un utilisateur ordinaire parvenait à l’exécuter, le constat serait : « Élévation de privilèges verticale : la fonction de suppression d’utilisateur est accessible à un rôle non administrateur par appel direct. Gravité élevée. Correction : contrôle central du rôle côté serveur pour toutes les routes d’administration ; test automatisé de la matrice d’accès. » Précisez que le lien d’administration était masqué dans l’interface : la protection n’était que visuelle.
À retenir
- Horizontale : accéder aux ressources d’un autre utilisateur de même niveau ; verticale : accéder à des fonctions d’un niveau supérieur.
- Masquer une fonction n’est pas la protéger : le serveur doit vérifier les droits à chaque requête.
- Les droits ne doivent jamais venir d’un élément contrôlable par le client (cookie lisible, paramètre, champ ajouté).
- L’affectation de masse se prévient par une liste blanche de champs modifiables.
- Une matrice rôle × fonction rend le test exhaustif et vérifiable.
Les contrôles d’accès techniques ne sont pas les seules failles possibles : certaines résident dans les règles de gestion elles-mêmes. C’est la leçon suivante.