Audit de sécurité : 3 - Énumération des paiements
L'audit de sécurité a révélé une faille importante : l'énumération des paiements.
L'application de base
L'utilisateur procède à une commande sur notre site. Nous lui proposons un règlement par CB et le redirigeons vers la plateforme de paiement en ligne. Une fois le paiement effectué, un contenu (node) est créé afin de récupérer les données relatives à la commande et l'utilisateur est redirigé vers une page de notre site lui indiquant le bon déroulement de l'opération. Cette page comporte deux informations sensibles : l'email de l'utilisateur et le numéro de commande.
Cette page de confirmation est générée automatiquement via un hook_menu() et comporte un argument : l'identifiant unique du node (nid). Exemple : "/paiements/66".
Il était stipulé dans le cahier des charges que la création d'une commande se faisait sans la création d'un compte "client" sur le site.
Exploitation
Les confirmations de paiement sont libres d'accès, aucune authentification n'est nécessaire.
La génération des id est incrémentale et donc prévisible. Il est possible pour un attaquant d'écrire un script dans le but est de brute forcer ces identifiants et ainsi récupérer les paiements présents sur le site.
Impact
Il devient possible pour un attaquant d'utiliser ces informations pour créer une campagne de phishing. Ces informations augmentent les chances de réussite de l'attaque.
Préconisation
Il est recommandé de limiter l'accès à ces pages en empêchant les utilisateurs non autorisés de pouvoir les lire. La génération d'un identifiant non devinable permettrait aussi d'éviter les attaques de type brute force.
Solution(s)
Ma première recommandation pré-developpement avait été de déconseiller l'accès à ces informations sans "compte" utilisateur. Mais cette demande était importante pour notre client. Nous l'avons donc intégré au cahier des charges final.
Néanmoins, je dois avouer que j'ai complètement sous-estimé l'accès à cette page de confirmation de paiement. Je me suis dit que la probabilité de trouver la bonne URL et donc le bon identifiant du contenu était faible.
Faible, oui mais pas tant que ça !!! Il suffit de passer une seule commande pour comprendre rapidement le mécanisme de l'URL.
Impossible pour nous de rentrer dans une mécanique de création de compte pour sécuriser l'accès. Mais l'idée d'avoir un second identifiant non devinable me paraissait être une bonne solution.
Après quelques recherches, je suis tombé sur une fonction propre à Drupal que je ne connaisait pas : drupal_random_key().
Cette fonction permet de générer une chaîne aléatoire sur 32 caractères et codée en base64. Parfait !!!
Lors de la création de la commande, je génère un identifiant supplémentaire via cette fonction. Cet identifiant est stocké dans le node correspondant à la commande via un champ. Il est aussi utilisé en tant que paramètres de requête (query parameters) dans notre URL de confirmation de paiement.
Pour exemple :
- Ancienne URL : "/paiements/66"
- Nouvelle URL : "/paiements/66?id=OfhW2Spw6SnNtbBL4CLE4d1SYOo5LLC6"
Lorsque cette page est consultée, je vérifie que le paramètre de requête "id" (ici, "OfhW2Spw6SnNtbBL4CLE4d1SYOo5LLC6") est bien identique à l'identifiant supplémentaire qui est stocké dans le contenu représenté par l'argument de l'URL (ici, "66"). Si cette condition est remplie, j'affiche les informations sensibles.
Au final, pour avoir accès à ces informations, il me faut connaitre l'identifiant du contenu, la clé utilisée pour le paramètre de la requête et la valeur du nouvel identifiant.
Conclusion
L'audit de sécurité a confirmé ce que je préconisais : un accès restreint à cette page.
Même si ce n'est pas cette solution qui a été mis en place, le client a désormais conscience que son choix comporte un risque.
La solution apportée avec la création d'un nouvel identifiant a été très rapide à mettre en place (une ligne de code) et est très satisfaisante sur le point technique.