← Retour aux tutoriels

WordPress — détection de webshells et WAF applicatif

Détection de webshells WordPress par signatures et intégrité, audit wp-cli des comptes et options, et règles WAF/ModSecurity adaptées à xmlrpc et wp-login.

Détection des webshells injectés dans une installation WordPress et mise en place de protections WAF sur les endpoints sensibles.

Menace

Après exploitation d’un plugin vulnérable, l’attaquant dépose typiquement un webshell PHP dans wp-content/uploads, wp-includes ou un faux plugin, et crée des comptes administrateurs ou injecte du code dans les options et les pieds de page de thèmes. Les endpoints xmlrpc.php et wp-login.php servent aux attaques par force brute et amplification.

Détection

Signatures de webshell par fonctions PHP dangereuses obfusquées :

grep -rlE "eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\(|assert\s*\(|create_function\s*\(|\\\$_(POST|GET|REQUEST|COOKIE)\s*\[" \
  /var/www/wp.exemple.fr/wp-content /var/www/wp.exemple.fr/wp-includes 2>/dev/null
grep -rlE "preg_replace\s*\(.*/e|\bpassthru\b|\bshell_exec\b|\bproc_open\b" /var/www/wp.exemple.fr 2>/dev/null

Audit des comptes administrateurs et des dernières connexions via wp-cli :

wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp option get siteurl
wp option get home

Détection de code injecté dans les options (URL externes dans des champs habituellement vides) :

wp db query "SELECT option_name, LEFT(option_value,80) FROM wp_options WHERE option_value REGEXP 'eval\\(|base64_decode|<script|wp_head' AND option_name NOT IN ('cron');"

Recherche de tâches cron suspectes injectées :

wp cron event list --fields=hook,next_run,recurrence

Mitigation

Comparaison du cœur et des plugins avec les sommes officielles, méthode fiable de détection d’altération :

wp core verify-checksums
wp plugin verify-checksums --all 2>&1 | grep -i 'does not match'

Désactivation de XML-RPC si non utilisé, et limitation de l’accès à wp-login.php par rate limiting (Nginx) :

location = /xmlrpc.php { deny all; return 403; }

limit_req_zone $binary_remote_addr zone=wplogin:10m rate=10r/m;
location = /wp-login.php {
    limit_req zone=wplogin burst=3 nodelay;
    limit_req_status 429;
}

Règles ModSecurity ciblées contre les patterns de webshell dans les uploads et le contenu POST :

SecRule REQUEST_FILENAME "@rx /wp-content/uploads/.*\.php$" \
    "id:1100010,phase:1,deny,status:403,log,msg:'PHP execution in uploads'"
SecRule REQUEST_BODY "@rx (?i:(eval|base64_decode|gzinflate|assert)\s*\()" \
    "id:1100020,phase:2,deny,status:403,log,msg:'Suspicious PHP payload in body'"

Nettoyage et reconstruction d’un plugin compromis depuis la source officielle :

wp plugin install <plugin> --version=<version> --force

Vérification

Confirmation de l’absence de webshell et d’intégrité du cœur :

find /var/www/wp.exemple.fr/wp-content/uploads -name '*.php' | wc -l
wp core verify-checksums && echo "Coeur intact"

Test que xmlrpc.php est inaccessible et que wp-login.php est limité :

curl -s -o /dev/null -w '%{http_code}\n' https://wp.exemple.fr/xmlrpc.php
for i in $(seq 1 6); do curl -s -o /dev/null -w '%{http_code} ' https://wp.exemple.fr/wp-login.php; done; echo

Le code 403 sur xmlrpc et l’apparition de 429 après quelques requêtes sur wp-login confirment l’application des règles.

Vous avez un projet sur ces sujets ?

Nous contacter →