En bref : pour vérifier la base de données à la recherche de code malveillant, parcourez un export ou une copie avec des requêtes en lecture seule pour y trouver des scripts injectés, des adresses étrangères et du code obscurci dans les contenus et les paramètres. Vérifiez aussi les triggers, les événements planifiés, les procédures stockées et les utilisateurs de la base de données. Ne nettoyez qu'après évaluation, et jamais par un rechercher-remplacer aveugle, car les données sérialisées seraient alors corrompues.
L'angle mort de nombreux nettoyages
Après une intrusion, la plupart des exploitants vérifient les fichiers. Ils remplacent le noyau du CMS, suppriment les fichiers PHP suspects et réinstallent les extensions. Le site semble ensuite sain, et une semaine plus tard il redirige à nouveau vers des pages étrangères. La raison se trouve souvent dans la base de données, reprise telle quelle lors du nettoyage.
Pour les attaquants, la base de données est une cachette attrayante. Un script dans un widget est diffusé sur chaque page. Un paramètre d'adresse modifié détourne tous les visiteurs. Un compte administrateur supplémentaire ouvre la porte pour plus tard. Rien de tout cela n'apparaît lors d'une analyse de fichiers.
Après PHP2Shell, les failles CVE-2026-109618 et CVE-2026-109619 permettant l'exécution de code sans authentification, la base de données fait donc partie de toute vérification. Qui peut exécuter du code sur le serveur peut, en règle générale, lire dans la configuration les identifiants de la base de données et y écrire à sa guise. La manière dont l'application communique avec la base de données est expliquée dans PHP et bases de données : PDO et mysqli.
Où se cache le code malveillant dans la base de données
| Emplacement | Contenu typique | Effet |
|---|---|---|
| Contenus (articles, pages, produits) | blocs <script>, liens invisibles, <iframe> |
code malveillant ou spam pour les visiteurs |
| Paramètres et options | adresse du site modifiée, scripts étrangers dans les widgets ou les options du thème | redirections, scripts sur toutes les pages |
| Tables d'utilisateurs | administrateurs supplémentaires | accès durable |
| Triggers | code qui modifie des contenus à chaque enregistrement | le code malveillant revient après le nettoyage |
| Événements planifiés | tâches récurrentes dans le serveur de base de données | modifications sans accès web |
| Utilisateurs de la base de données | comptes supplémentaires aux droits étendus | accès en contournant le CMS |
Les trois dernières lignes sont particulièrement souvent négligées, car elles ne se trouvent pas dans les tables du CMS, mais dans le serveur de base de données lui-même.
Pour les boutiques en ligne, un emplacement mérite une attention particulière : les paramètres par lesquels des scripts supplémentaires sont intégrés dans la caisse et la finalisation de commande. Un script étranger à cet endroit peut lire les saisies des clients avant qu'elles ne partent chiffrées vers le prestataire de paiement. Vérifiez donc de façon ciblée tous les champs de code d'en-tête et de pied de page, les intégrations de suivi et les paramètres de design qui agissent sur les pages de caisse.
Pensez aussi aux autres bases de données sur le même serveur. Les copies de test et de préproduction, les anciens états de projet ou les bases d'autres sites se trouvent souvent à côté et sont accessibles avec les mêmes identifiants. Ce qui est trouvé dans la base de production devrait aussi y être vérifié avant qu'une copie ne serve à nouveau de base plus tard.
Vérifier la base de données : contenus et paramètres
Travaillez sur un export. Il sert à la fois de préservation des preuves et de base de recherche. L'export peut être parcouru comme fichier texte sans solliciter la base de données en production :
# Créer un export cohérent (les identifiants sont demandés)
mysqldump --single-transaction --routines --triggers --events -u BENUTZER -p DATENBANK > export.sql
sha256sum export.sql > export.sql.sha256
# Compter les motifs typiques dans l'export
grep -oiE '<script[^>]{0,80}' export.sql | sort | uniq -c | sort -rn | head -30
grep -oiE '(eval\(|base64_decode|fromCharCode|document\.write|atob\()' export.sql | sort | uniq -c
grep -oiE 'https?://[a-z0-9.-]+' export.sql | sort | uniq -c | sort -rn | head -50
La dernière requête liste, par fréquence, tous les domaines présents dans la base de données. Les domaines étrangers que personne dans l'équipe ne connaît sont les principaux candidats. Pour des requêtes ciblées dans la base de données elle-même, les commandes SELECT conviennent. L'exemple utilise des tables WordPress ; le préfixe et les noms de tables des autres systèmes figurent dans leur documentation :
-- Paramètres contenant des scripts ou des fonctions suspectes
SELECT option_name, LENGTH(option_value) AS laenge
FROM wp_options
WHERE option_value LIKE '%<script%'
OR option_value LIKE '%eval(%'
OR option_value LIKE '%base64_decode%'
ORDER BY laenge DESC;
Les entrées au contenu très volumineux méritent une attention particulière. Le guide Trouver les portes dérobées dans WordPress donne des informations sur les cachettes propres à WordPress.
Triggers, événements et comptes de base de données
Les triggers et événements planifiés ne sont tout simplement pas prévus dans de nombreuses applications web. S'ils existent, quelqu'un devrait pouvoir expliquer pourquoi. Ces requêtes ne font que lire dans les tables système de MySQL et MariaDB :
-- Triggers dans toutes les bases de données
SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_OBJECT_TABLE, CREATED
FROM information_schema.TRIGGERS;
-- Événements planifiés
SELECT EVENT_SCHEMA, EVENT_NAME, STATUS, CREATED, LAST_EXECUTED
FROM information_schema.EVENTS;
-- Procédures et fonctions stockées
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema');
-- Utilisateurs de la base de données
SELECT user, host FROM mysql.user ORDER BY user;
Si votre application utilise PostgreSQL ou un autre système de base de données, les tables système et les commandes portent d'autres noms ; le principe reste le même. Pour l'analyse, il est recommandé d'utiliser un utilisateur de base de données dédié, autorisé uniquement à lire. Ainsi, même une faute de frappe dans une requête ne peut rien modifier, et la vérification ne laisse aucune trace dans les données.
Un trigger qui ajoute du code lors de l'enregistrement d'un article explique pourquoi le code malveillant revient après chaque nettoyage. Les utilisateurs inconnus de la base de données font partie de la vérification des comptes au même titre que les comptes administrateur du CMS ; en savoir plus dans Trouver les comptes administrateur étrangers dans le CMS et sur le serveur.
Nettoyer proprement sans détruire de données
Le nettoyage est la partie la plus délicate. Cet ordre a fait ses preuves :
- Évaluer les trouvailles : chaque résultat est classé comme connu, explicable et sans danger, ou étranger. Les codes de suivi et de cartes sont souvent légitimes.
- Fermer la porte d'entrée : amener PHP à une version corrigée (8.5.12, 8.4.27, 8.3.36, 8.2.35 ou backport confirmé), redémarrer tous les workers, mettre à jour l'application.
- Supprimer de façon ciblée : nettoyer les contenus étrangers via l'application ou avec des outils qui traitent correctement les données sérialisées. Supprimer les triggers, événements et comptes étrangers.
- Changer les identifiants : nouveau mot de passe de base de données, à inscrire dans la configuration de l'application, droits de l'utilisateur de l'application limités à sa propre base de données.
- Revérifier : répéter les requêtes de recherche après quelques jours.
Ne remplacez rien par rechercher-remplacer dans l'export SQL. Les données PHP sérialisées stockent la longueur de chaque chaîne. Si la longueur change, le paramètre devient illisible et le site peut tomber en panne.
Les redirections qui se produisent malgré une base de données saine ont leur cause dans une autre couche. L'article Le site redirige vers des pages étrangères aide à la délimiter.
Comment PHP SOS aide-t-il concrètement ?
- Nous sauvegardons la base de données avec somme de contrôle et parcourons l'export à la recherche de scripts, de domaines étrangers et de code obscurci.
- Nous vérifions les triggers, événements, procédures et utilisateurs de la base de données situés en dehors du CMS.
- Nous nettoyons les trouvailles dans le cadre de la suppression de logiciels malveillants, sans endommager les données sérialisées.
- Nous limitons les droits de l'utilisateur de l'application et répétons la vérification après le nettoyage.
Questions fréquentes
Du code malveillant peut-il se cacher dans la base de données ?
Oui. Les attaquants écrivent du JavaScript dans des articles, des descriptions de produits ou des widgets, modifient les paramètres d'adresse pour des redirections ou créent des comptes administrateur. Dans MySQL et MariaDB, des triggers et des événements planifiés peuvent en outre modifier des données. Une analyse de fichiers n'en trouve rien.
Comment parcourir la base de données sans rien casser ?
Travaillez sur un export ou une copie et n'utilisez que des requêtes SELECT. Un export peut en outre être parcouru avec grep comme fichier texte. Les modifications ne se font qu'une fois chaque trouvaille évaluée et une sauvegarde disponible.
Pourquoi ne pas nettoyer les trouvailles par rechercher-remplacer dans l'export ?
De nombreux CMS enregistrent les paramètres sous forme de données PHP sérialisées, dans lesquelles la longueur de chaque chaîne est stockée avec elle. Un remplacement modifie la longueur, mais pas son indication, et le paramètre devient illisible. Nettoyez via l'application ou avec des outils qui comprennent les données sérialisées.
Tout script dans la base de données est-il suspect ?
Non. Les codes de suivi, les cartes, les vidéos ou les widgets de chat sont eux aussi enregistrés sous forme de script. L'essentiel est de savoir si quelqu'un dans l'équipe connaît l'entrée et l'adresse de destination.