Broken access control : prévenir IDOR et élévation de privilèges

Prévenir les failles d’accès dans PrestaShop (IDOR, élévation de privilèges) : principes «deny by default», voters Symfony, tests fonctionnels, logs et checklist pratique.

Écrans d'ordinateur avec code, alertes de vulnérabilité et cadenas.

Table des matières :

  1. Broken Access Control : le problème réel derrière le mot-clé OWASP
  2. IDOR : où ça arrive dans PrestaShop (et pourquoi)
  3. Élévation de privilèges : BO, rôles employés, et routes Symfony/legacy
  4. Patterns de prévention : “deny by default” + contrôle d’accès au niveau ressource
  5. Tester, journaliser, détecter : transformer un bug “silencieux” en signal exploitable
  6. Checklist opérationnelle (PrestaShop 8.1/9, PHP 8.2+) : éviter l’IDOR et l’élévation de privilèges en prod

Broken Access Control : le problème réel derrière le mot-clé OWASP

Le Broken Access Control (A01 dans l’OWASP Top 10 2021) recouvre une idée simple : l’application ne fait pas respecter, côté serveur, les règles d’autorisation (qui a le droit de faire quoi, sur quelle ressource). OWASP illustre l’ampleur du sujet avec deux chiffres très concrets : dans les données qui ont servi au Top 10 2021, 94% des applications étaient testées pour des failles de contrôle d’accès, et la catégorie affiche le taux d’incidence maximal le plus élevé (55,97%) parmi les 10 catégories (OWASP Top 10 2021, A01). En e‑commerce, ce n’est pas “théorique” : un IDOR sur une commande ou une adresse, c’est une fuite de données personnelles (et un incident RGPD) ; une élévation de privilèges dans le BO, c’est potentiellement une compromission complète.

Dans l’UE, cette dimension “incident” n’est pas qu’un enjeu réputationnel : la sécurité doit être conçue et maintenue comme une mesure de conformité. Le RGPD rappelle explicitement l’exigence de mesures adaptées :

« Le responsable du traitement et le sous-traitant mettent en œuvre des mesures techniques et organisationnelles appropriées afin de garantir un niveau de sécurité adapté au risque… »
— Règlement (UE) 2016/679 (RGPD), article 32 (2016)

PrestaShop est un terrain fertile pour ces erreurs, pas parce que le core est “mauvais”, mais parce que l’écosystème modules + overrides + contrôleurs legacy + Symfony (8.x/9.x) multiplie les surfaces où l’autorisation peut être oubliée. Le piège récurrent : confondre authentification (l’utilisateur est bien connecté) et autorisation (il a le droit d’accéder à cette ressource). Un client authentifié n’a pas automatiquement le droit d’accéder à toutes les commandes ; un employé authentifié n’a pas automatiquement le droit d’appeler tous les endpoints d’admin.

Sur PrestaShop 8.1/8.2 et PrestaShop 9 (contexte 2026), on voit aussi un problème structurel : une partie des routes Symfony bénéficie d’un modèle de sécurité plus explicite (roles, voters, isGranted()), tandis que le legacy a des garde-fous hétérogènes (tokens, AdminController::access(), restrictions Tab/profils). Résultat : un module peut introduire une route “à côté” qui contourne l’intention de sécurité du back-office.

Définition utile (et opérationnelle) :

  • IDOR (Insecure Direct Object Reference) : l’utilisateur contrôle un identifiant (ex. id_order=123) et le serveur renvoie/modifie l’objet sans vérifier qu’il lui appartient (ou qu’il a le droit d’y accéder).
  • Élévation de privilèges : l’utilisateur obtient des droits non prévus (ex. un employé “logistique” déclenche une action “super admin” via une route mal protégée).

Deux nuances importantes en audit (souvent oubliées) :

  • IDOR “en lecture” vs “en écriture” : lire une commande d’un autre client est déjà grave, mais modifier une adresse, un statut ou un remboursement peut être catastrophique (fraude, sabotage, pertes financières).
  • IDOR “direct” vs “indirect” : remplacer id_order=123 est l’exemple simple, mais l’IDOR peut aussi passer par une référence (ex. reference=ABCDX) ou un identifiant “métier” exposé dans un PDF, un email, ou un export.

Pour les aspects API (REST, endpoints, webhooks), gardez en tête la différence entre “endpoint” et “ressource” ; un endpoint peut être authentifié et pourtant exposer une ressource non autorisée. Référence interne utile : Endpoint API : définition, méthodes HTTP et bonnes pratiques REST.

IDOR : où ça arrive dans PrestaShop (et pourquoi)

Le cas d’école en e‑commerce : un client connecté consulte le détail d’une commande via une URL du type /order-detail?id_order=123 (ou un contrôleur de module similaire). Si le contrôleur se contente de charger la commande par id_order et d’afficher le résultat, l’attaquant n’a qu’à incrémenter l’ID pour extraire d’autres commandes (numéros de téléphone, adresses, montants). C’est un IDOR “pur” : pas besoin d’injection SQL, pas besoin de XSS, juste une autorisation manquante.

Dans PrestaShop, cette classe de bug se retrouve surtout dans :

1) contrôleurs front office de modules (ModuleFrontController), souvent écrits rapidement et testés uniquement en “happy path” ;
2) AJAX endpoints exposés via ajax=1 ou des routes custom qui renvoient du JSON ;
3) tâches d’automatisation (imports/exports, connecteurs ERP) où on manipule des IDs (produits, clients, commandes) sans vérifier l’appartenance ni le contexte boutique.

Un schéma typique en production (mini-scénario réaliste) :

  • vous avez un module “portail client” qui propose Télécharger ma facture ;
  • l’URL ressemble à /module/portail/facture?id_order=8127;
  • en test, tout est correct parce que vous essayez uniquement avec le même compte ;
  • en exploitation, un utilisateur malveillant automatise une boucle 8120..8200 et récupère des PDF contenant identité + adresse + détails de commande.

Sur les intégrations SI, l’IDOR est fréquemment introduit par une logique “outil interne” qui finit exposée publiquement (ex. endpoint de portail client pour récupérer un BL ou un statut de commande). Ce n’est pas un problème abstrait : dès qu’un portail, un connecteur ou un webhook retourne des entités sensibles, vous devez traiter l’autorisation comme une exigence de conception, pas comme un “if” tardif. Pour les patterns d’intégration (API, fichiers, webhooks) : Portail client : intégration ERP/WMS/TMS via API, fichiers et webhooks.

Un autre point souvent oublié : multi-boutique et scope de données. Un IDOR ne concerne pas uniquement l’utilisateur final ; il peut exister entre boutiques (shop A lit une commande shop B) si vous oubliez de filtrer par id_shop/id_shop_group selon le contexte. Ce bug est discret en recette (où tout est souvent dans une boutique unique) et apparaît en production quand le catalogue et les commandes sont segmentés.

Pour rendre l’audit plus systématique, une grille simple (à adapter) :

Surface PrestaShop Erreur fréquente Contrôle indispensable
FO module (ModuleFrontController) “Client connecté = OK” Ownership + shop scope + 403/404 cohérents
Endpoint AJAX JSON Paramètre id_* non revalidé Revalidation serveur à chaque requête
Export / import / connecteur Ids “internes” traités comme fiables AuthN et AuthZ, traçabilité, moindre privilège
Multi-boutique Oubli de id_shop Filtre par scope boutique (lecture/écriture)

Exemple minimal (front) : vérifier l’ownership avant d’afficher une commande

Contexte : PrestaShop 8.1/9, PHP 8.2+. Dans un ModuleFrontController, le réflexe doit être : deny by default, puis vérifier (1) authentification, (2) propriété, (3) périmètre boutique.

public function initContent()
{
    parent::initContent();

    if (!$this->context->customer->isLogged()) {
        Tools::redirect('index.php?controller=authentication');
    }

    $idOrder = (int) Tools::getValue('id_order');
    $order = new Order($idOrder);

    // 1) Existence + cohérence minimale
    if (!Validate::isLoadedObject($order)) {
        throw new PrestaShopException('Order not found');
    }

    // 2) Ownership : le point central anti-IDOR
    if ((int) $order->id_customer !== (int) $this->context->customer->id) {
        header('HTTP/1.1 403 Forbidden');
        exit;
    }

    // 3) Optionnel mais recommandé : scope multi-shop
    if ((int) $order->id_shop !== (int) $this->context->shop->id) {
        header('HTTP/1.1 403 Forbidden');
        exit;
    }

    // ... render
}

Ce code n’est pas “joli”, mais il fait le job : aucune dépendance externe, autorisation server-side, sortie explicite. Évitez les contrôles basés uniquement sur un token GET ; un token est utile contre le CSRF, pas contre l’IDOR si l’ID est devinable.

  • Réduire l’exposition d’identifiants séquentiels : ce n’est pas une solution (l’autorisation reste obligatoire), mais remplacer un id_order incrémental par un identifiant non devinable pour les URLs publiques (ex. UUID) réduit l’énumération opportuniste.
  • Choisir 404 vs 403 avec intention : 403 est correct quand la ressource existe mais est interdite ; 404 limite parfois la divulgation d’existence. Dans tous les cas, ne laissez jamais l’application “répondre 200 avec des données” en cas de doute.

Élévation de privilèges : BO, rôles employés, et routes Symfony/legacy

L’élévation de privilèges en PrestaShop se produit souvent à cause d’un mauvais alignement entre l’UI et le serveur : l’interface masque un bouton à un profil, mais le contrôleur exécute quand même l’action si on appelle l’URL directement. Dans le legacy, beaucoup de devs pensent être “couverts” par les onglets (Tabs) et les profils employés, puis ajoutent une action custom en oubliant le check d’accès.

Techniquement, côté back-office legacy, le contrôle d’accès repose sur plusieurs couches : l’authentification employé, la permission liée au Tab, et des contrôles dans AdminController ($this->access('view'), $this->access('edit'), etc.). Problème : un module peut exposer un script ajax.php ou un contrôleur admin custom sans réutiliser correctement ces mécanismes. Le résultat : un employé “catalogue” peut déclencher une action “commandes” ou “modules”, voire exécuter un export massif.

Un cas fréquent en audit : une action “maintenance” (ex. recalcul d’index, purge de cache, export CSV) est rangée dans un onglet accessible, mais l’URL d’exécution (ou l’endpoint AJAX) n’effectue pas de check de permission. Résultat : un profil low‑privilege peut appeler une action high‑impact — et comme c’est “interne”, c’est souvent moins surveillé qu’une attaque externe.

Côté Symfony (particulièrement avec PrestaShop 9 et son admin modernisé), les risques se déplacent vers :

  • routes annotées ou configurées sans isGranted() / security.yaml correct ;
  • confusion entre rôles “techniques” Symfony et permissions “métier” PrestaShop ;
  • fonctionnalités d’impersonation (switch_user) trop permissives.

Sur ce dernier point, si vous utilisez l’impersonation pour le support, ne faites pas “au feeling”. Symfony documente clairement le mécanisme, mais sans restriction stricte (Voter, attributs, journalisation), c’est un escalier vers l’abus interne. Référence interne : Symfony switch_user : limiter l’impersonation avec Voter et isGranted.

Enfin, ne sous-estimez pas l’élévation de privilèges via API d’administration si vous exposez des endpoints (internes ou publics) sans scoper précisément les droits. PrestaShop 9 introduit des patterns OAuth/API Platform, mais ils n’annulent pas les erreurs de design (scope trop large, absence de “resource ownership”). Référence interne : API d’administration PrestaShop 9 : OAuth, API Platform v3, endpoints CQRS.

Patterns de prévention : “deny by default” + contrôle d’accès au niveau ressource

Le seul pattern fiable contre Broken Access Control est documenté depuis des décennies : contrôle côté serveur, au plus près de la ressource. Un rappel qui reste pertinent : Saltzer & Schroeder (1975) formalisent le principe de least privilege (moindre privilège) : donner strictement les droits nécessaires, rien de plus. Dans PrestaShop, ça se traduit en pratique par : (1) réduire les permissions Tab/profils, (2) limiter les clés API, (3) éviter les contrôleurs “god mode” dans les modules.

Une règle d’architecture qui aide à éviter les oublis : l’autorisation doit être non contournable. Autrement dit, ne placez pas la logique de permission uniquement dans l’UI, ni uniquement dans un contrôleur “principal” si d’autres handlers peuvent appeler la même action (AJAX, routes alternatives, cron, API). Idéalement, l’autorisation est vérifiée :

  • au niveau contrôleur et (quand c’est possible) dans la couche service qui réalise l’action ;
  • de façon homogène (mêmes règles, mêmes décisions) ;
  • avec une trace exploitable (logs).

Symfony : privilégier Voters et checks explicites

Si vous écrivez des contrôleurs Symfony (PrestaShop 8+ / 9), arrêtez de raisonner en “ROLE_ADMIN et c’est bon”. Le bon niveau de granularité est “peut modifier cette commande” ou “peut lire ce client”. Concrètement :

  • utilisez un Voter qui encapsule la logique d’autorisation (ownership, shop scope, état de commande) ;
  • utilisez #[IsGranted('ORDER_VIEW', subject: 'order')] ou $this->denyAccessUnlessGranted() ;
  • testez vos voters en unit tests (entrées multiples, profils multiples).

Même si PrestaShop n’est pas un Symfony “pur”, ce pattern rend l’autorisation visible et réutilisable. Surtout, il réduit le risque de “je vérifie dans une action, j’oublie dans une autre”. Le défaut typique des modules est la duplication de checks ad hoc.

Legacy/Module : ne pas confondre token BO et autorisation

Dans le back-office legacy, le token protège en partie contre certains appels non autorisés, mais il ne remplace pas :

  • un check de permission ($this->access('view'), edit, delete) ;
  • un check de périmètre (multi-boutique) ;
  • un check métier (ex. un profil “SAV” peut lire, mais pas éditer un remboursement).

Si votre module ajoute un onglet admin, assurez-vous que les permissions sont correctement déclarées et que les actions sensibles sont vérifiées sur chaque handler, pas uniquement sur l’écran principal.

APIs / Webservice : scoper la clé et re-vérifier l’accès au niveau entité

Le Webservice PrestaShop et les APIs custom sont un vecteur classique : on met une clé, on pense que c’est “sécurisé”, puis on expose trop de ressources. La règle : authentifier n’est pas autoriser. Une clé API doit être limitée (ressources, méthodes), et chaque requête doit être validée par rapport au contexte (boutique, rôle technique, ownership).

Un point pratique qui évite beaucoup d’accidents : définissez une matrice “clé → ressources → méthodes → scope” (même en simple tableau dans votre repo). Exemple : la clé “ERP‑ReadOrders” ne peut faire que GET /orders et GET /order/{id} sur une boutique précise, et jamais PUT/POST/DELETE.

Références internes utiles :

Côté standards, OWASP insiste (ASVS, mais aussi Top 10) sur un point qui doit guider vos revues de code : le contrôle d’accès doit être enforced server-side et revalidated on every request, pas seulement lors de la connexion.

Tester, journaliser, détecter : transformer un bug “silencieux” en signal exploitable

Un Broken Access Control bien exploité laisse parfois peu de traces visibles côté client. Vous devez donc combiner tests préventifs et observabilité. En 2026, il n’y a aucune excuse pour livrer un endpoint qui n’a jamais été appelé en “profil interdit” en CI.

Un bon indicateur de maturité : vous êtes capables de répondre vite à ces questions, preuves à l’appui :

  • “Quels endpoints renvoient le plus de 403 ces 7 derniers jours ?”
  • “Sur quelles ressources (commandes, clients) les refus se concentrent-ils ?”
  • “Un employé a‑t‑il déclenché une action sensible hors de ses horaires habituels ?”

Tests automatisés (fonctionnels) : le minimum viable

Sur les routes Symfony, un test fonctionnel doit vérifier au moins :

  • 401/302 si non authentifié,
  • 403 si authentifié mais non autorisé,
  • 200/204 uniquement si autorisé.

Sur le legacy, vous pouvez difficilement atteindre le même confort, mais vous pouvez au moins écrire des tests d’intégration qui appellent les endpoints de modules (front et ajax) avec des sessions différentes. L’objectif n’est pas la couverture parfaite, mais de capturer les régressions : “on a ajouté une action export, elle est bien interdite au profil X”.

Pour sécuriser votre chaîne de livraison modules + dépendances, le sujet “provenance / CI / validation” n’est pas décoratif : un module tiers peut introduire une route non protégée. Référence interne : CI PrestaShop : provenance, SBOM et validation automatique des modules.

Logs applicatifs : tracer les décisions d’autorisation

Deux logs sont particulièrement utiles :

1) log d’accès refusé (403) enrichi (route, ID ressource demandé, userId/employeeId, shopId) ;
2) log d’action sensible (export, remboursement, changement d’email, changement de statut de commande) avec l’identité et l’IP.

Sans ça, vous ne distinguerez jamais “un bot qui scanne” d’un abus interne. Pour l’instrumentation globale (PHP/MySQL/JS) : PrestaShop monitoring d’erreurs : logs PHP, MySQL, JavaScript et alertes e-mail.

Astuce simple mais efficace : logguez la décision (autorisé/refusé) et la raison (ownership mismatch, scope boutique, permission Tab manquante). En incident response, ça fait gagner un temps considérable.

WAF / rate limiting : pas une rustine, mais un filet de sécurité

Un WAF ne corrige pas un IDOR, mais il peut limiter l’exploitation (ex. blocage d’une enumeration d’IDs par rafale). Sur PrestaShop, un WAF mal réglé casse souvent le checkout ; le sujet est donc de réduire les faux positifs tout en gardant une barrière contre l’automatisation hostile. Référence interne : WAF PrestaShop : réduire les faux positifs et sécuriser le checkout.

Ajoutez en complément du rate limiting au plus près (HAProxy/Nginx/API Gateway) pour les endpoints à risque (recherches, downloads, exports). Si vous avez déjà une passerelle API, les patterns d’auth et de limitation y sont plus simples à centraliser : API Gateway : authentification, rate limiting et routage des microservices.

Checklist opérationnelle (PrestaShop 8.1/9, PHP 8.2+) : éviter l’IDOR et l’élévation de privilèges en prod

Avant de toucher au code : pré-requis. Travaillez sur un environnement reproductible (local/CI), versionnez vos changements, et assurez-vous d’avoir des sauvegardes (fichiers + base). Toute modification de permissions (Tabs, rôles, scopes API) doit être testée sur un jeu de données réaliste (multi-boutique si concerné).

1) Pour chaque route / contrôleur (FO/BO/AJAX/API), documentez :

  • qui peut l’appeler (anonyme, client, employé, service),
  • sur quelles ressources (commande, client, produit),
  • sur quel périmètre (boutique, groupe de boutique, pays, entrepôt),
  • quelles méthodes (GET/POST/DELETE) et quels side effects.

Cette étape force l’équipe à expliciter l’autorisation. Elle s’aligne directement avec les bonnes pratiques de conception REST : bonnes pratiques pour concevoir des endpoints REST.

2) Contrôles anti-IDOR à appliquer systématiquement :

  • vérifier l’ownership (id_customer, id_employee, id_supplier, etc.) sur chaque lecture/écriture ;
  • vérifier le scope multi-boutique (id_shop) si la donnée est shoppable ;
  • ne pas se baser sur l’UI (“le lien n’est pas affiché”) ;
  • refuser par défaut : si une ressource n’est pas chargée/valide, retourner 404 ; si elle est valide mais non autorisée, retourner 403.

3) Permissions employé : réduire l’attaque interne :

  • audit des profils et permissions Tab (le “tout le monde admin” est une dette de sécurité) ;
  • journalisation des actions sensibles ;
  • restrictions fortes sur l’impersonation (si activée), avec contrôle fin (Voter) et logs dédiés.

Pour l’approche globale “moindre privilège + audits + automatisation”, un article connexe couvre déjà la logique d’architecture : Automatisation PrestaShop : sécurité RGPD, moindre privilège et journaux d’audit.

4) API/Webservice :

  • une clé = un usage (évitez les clés “omni” partagées),
  • méthodes strictes (read-only si possible),
  • rate limiting,
  • contrôle d’accès au niveau entité (ownership/scope), même si la clé est “interne”.

Voir : Webservice PrestaShop : activer l’API et créer une clé d’accès et API : sécuriser apikey, limiter le débit et renforcer la conformité.

5) Durcissement et maintenance :

  • mises à jour core/modules (les correctifs de sécurité incluent régulièrement des problèmes de contrôles d’accès),
  • headers de sécurité (ne corrigent pas l’IDOR, mais réduisent d’autres vecteurs),
  • surveillance 403/404 anormales (indicateur d’enumeration).

Pour la baseline de durcissement PrestaShop : Sécurité PrestaShop : mises à jour, SSL et durcissement .htaccess et, côté headers : HTTP Security Headers en PHP : guide CSP, HSTS, X-Frame-Options.

Références externes (à lire, pas à survoler) :

Si vous ne devez retenir qu’un invariant : tout identifiant contrôlé par l’utilisateur est hostile jusqu’à preuve du contraire, et la preuve, c’est un check d’autorisation server-side systématique (ownership + scope + permission), testé et observable.


À lire aussi