« Pourquoi vois-je des scores de prospects qui ne sont pas les nôtres ? » Cette question, posée par un commercial d’une filiale à son support technique, a déclenché l’audit qui fait l’objet de ce retour d’expérience. Le groupe en question exploitait une extension WordPress de scoring de leads par intelligence artificielle, mutualisée entre plusieurs sociétés partageant la même infrastructure d’hébergement mais opérant des activités commerciales distinctes.
Chaque société du groupe disposait de son propre compte au sein de l’extension, censé cloisonner ses prospects et les scores calculés par le modèle. Dans les faits, un partenaire commercial externe, disposant d’un accès limité en lecture seule à ses propres scores, pouvait consulter ceux calculés pour les prospects d’une autre société du groupe simplement en modifiant un identifiant dans l’URL de la requête API.
Symptôme : un identifiant numérique trop prévisible dans l’URL
La route REST exposée par l’extension suivait un schéma classique : /wp-json/scoring/v1/leads/{compte_id}/scores. L’identifiant de compte était un entier auto-incrémenté, attribué séquentiellement à chaque nouvelle société inscrite sur la plateforme. Un partenaire curieux, en changeant simplement ce chiffre dans l’URL, obtenait une réponse JSON complète contenant les scores et certaines métadonnées des prospects d’un autre compte.
L’anomalie a été repérée par hasard, lors d’un test manuel effectué pour vérifier un tout autre comportement de l’API. Aucune alerte automatisée n’avait signalé ce comportement, faute de surveillance sur les schémas d’accès à cette route spécifique.
Diagnostic : une autorisation vérifiant l’authentification mais pas l’appartenance
L’examen du code de la fonction de rappel associée à la route a révélé la cause exacte : la fonction permission_callback vérifiait uniquement que la requête provenait d’un utilisateur authentifié disposant d’un jeton API valide, sans jamais comparer l’identifiant de compte demandé à celui associé à ce jeton.

'permission_callback' => function ( $request ) {
// Vérifie l'authentification, pas l'appartenance au compte demandé
return is_user_logged_in() || rest_api_valid_token( $request );
},
Ce type d’erreur est fréquent dans les architectures multi-comptes construites progressivement : l’authentification a été mise en place en premier, l’autorisation fine par ressource est arrivée plus tard, et un endpoint plus ancien est parfois passé entre les mailles du filet lors de la généralisation du contrôle d’appartenance.
Correctif : une clause d’appartenance systématique avant toute réponse
La correction a consisté à ajouter, dans chaque fonction de rappel exposant des données liées à un compte, une vérification explicite que l’identifiant de compte demandé correspond bien à celui associé au jeton ou à l’utilisateur authentifié :
'permission_callback' => function ( $request ) {
$compte_token = rest_api_get_compte_id_from_token( $request );
$compte_demande = (int) $request->get_param( 'compte_id' );
return $compte_token && $compte_token === $compte_demande;
},
Au-delà de ce correctif ponctuel, l’équipe a mené une revue systématique de toutes les routes REST exposées par l’extension, à la recherche d’autres endpoints présentant le même défaut de cloisonnement.
Prévention : tester le cloisonnement à chaque nouvelle route
Ce type de faille se prévient efficacement par un test systématique et documenté à chaque ajout de route REST touchant à une ressource associée à un compte :
- Créer deux comptes de test distincts avant toute mise en production d’une nouvelle route.
- Tenter délibérément d’accéder aux données du compte B avec les identifiants du compte A.
- Automatiser ce test dans la suite d’intégration continue plutôt que de le réserver à une vérification manuelle ponctuelle.
L’utilisation d’identifiants séquentiels prévisibles aggrave également ce type de faille : remplacer les identifiants de compte auto-incrémentés par des identifiants UUID rend l’énumération manuelle beaucoup plus difficile, sans pour autant remplacer un contrôle d’appartenance correctement implémenté.
Un identifiant qui se devine ne protège rien à lui seul, mais son absence de prévisibilité gagne un temps précieux avant qu’une faille de cloisonnement soit découverte et exploitée à grande échelle.
En résumé
Cette faille illustre un piège classique des extensions multi-comptes : une authentification fonctionnelle qui masque l’absence de vérification d’appartenance sur une ressource précise. La pertinence du modèle de scoring lui-même n’entre pas en ligne de compte ici ; ce qui compte, c’est que chaque route exposant des données propres à un compte vérifie explicitement cette appartenance avant de répondre, et que cette vérification soit testée systématiquement plutôt que supposée acquise une fois pour toutes.