WordPress · 9 min · publié le 1 mars 2025

wp-cron et malware : des tâches qui se réenregistrent

Supprimer un événement wp-cron ne sert à rien si une option, un mu-plugin ou un thème le réenregistre au prochain chargement. Voici où la tâche vit vraiment, comment la figer, et ce qu'il ne faut pas vider à l'aveugle.

Réponse directe

Une option en base ré-ajoute la tâche après suppression. Vider le cron transitoire sans retirer l'option ne suffit pas. Regardez wp_options et les mu-plugins.

wp-cron malware cron wordpress revient scheduled event hack

Ce que wp-cron est, et ce qu'il n'est pas

wp-cron n'est pas le cron Unix. C'est une file d'événements stockée en base (`cron` dans `wp_options`) que WordPress parcourt quand quelqu'un charge une page — ou quand un vrai cron HTTP appelle `wp-cron.php`. Un malware s'y enregistre comme n'importe quel plugin de sauvegarde : un hook, un intervalle, une fonction. La différence, c'est que la fonction envoie du spam, réécrit un fichier, ou rappelle un serveur.

Désactiver wp-cron dans `wp-config` (`DISABLE_WP_CRON`) arrête le déclenchement par visiteur. Ça n'efface pas la file, et ça n'empêche pas un cron système ou un voisin d'appeler encore `wp-cron.php`. Beaucoup de gens « coupent le cron » et croient avoir gagné ; la tâche est toujours là, prête au prochain hit.

L'inventaire se fait à froid : `wp cron event list` (WP-CLI), ou lecture de l'option `cron`. Notez hook, recurrence, next run, et le plugin qui a enregistré l'action si vous le voyez. Copiez avant d'effacer. Une file légitime (WooCommerce, Updraft, newsletter) mélangée au malware se reconstruit mal à la main.

Un événement nommé comme le cœur (`wp_version_check`) peut être un leurre. C'est la fonction callback et le fichier qui comptent, pas le libellé.

La tâche qui revient après suppression

Vous videz l'événement dans un plugin « cron manager », vous rechargez, il est revenu. Trois mécanismes classiques. Un `add_action('init')` ou `admin_init` qui rappelle `wp_schedule_event` si l'événement n'existe pas — code vivant dans le thème enfant, un plugin abandonné, ou un mu-plugin. Une option maison (`wp_options`) lue à chaque boot qui réarme. Un drop-in (`object-cache.php`, `advanced-cache.php`) qui s'exécute trop tôt pour que votre manager le voie.

Traiter le symptôme (la ligne dans la file) sans l'enregistreur, c'est recommencer toutes les cinq minutes. Cherchez le callback : `wp cron event list` puis grep du nom de hook dans `wp-content`. Si le hook n'apparaît dans aucun fichier du thème ou des plugins, il est en base (option sérialisée qui `eval`, ou objet stocké) ou dans un fichier hors `wp-content`.

C'est le même schéma qu'une injection dans wp_options : le fichier PHP « officiel » est propre, la persistance est ailleurs.

L'option en base qui ré-ajoute l'événement

Cherchez dans `wp_options` les autoload `yes` récentes, les options dont le nom ne veut rien dire (`wp_cache_key`, `cdn_setting`, un hash), et celles dont la valeur contient `http`, `eval`, `base64`, un domaine inconnu, ou un `wp_schedule`. Ne les videz pas en masse : `theme_mods`, `active_plugins`, `rewrite_rules` cassent le site.

Une option de cache ou de « licence » est un camouflage fréquent. Grep SQL sur `cron`, `eval`, `<script`, `fromCharCode`. Exportez la ligne avant UPDATE. Restaurer « la base d'il y a un mois » sans lire ramène souvent le même réarmeur — voir base WordPress injectée.

Les transitoires (`_transient_`, `_site_transient_`) peuvent porter une copie de la file ou un payload. Les vider (`wp transient delete --all`) calme parfois l'affichage, jamais un `add_action` dans un mu-plugin. Faites les deux contrôles, dans cet ordre : code qui s'enregistre, puis options, puis transitoires.

  • Export de l'option `cron` avant toute suppression.
  • Grep des hooks suspects dans wp-content et la base.
  • Autoload récents : lire, ne pas TRUNCATE.

Mu-plugins, must-use et drop-ins

Les must-use (`wp-content/mu-plugins`) s'exécutent sans être dans la liste des plugins. C'est l'endroit préféré d'une persistance « le site a l'air propre ». Un fichier d'une vingtaine de lignes qui schedule un HTTP sortant ou réécrit `index.php` suffit. L'admin WordPress ne les désactive pas d'un clic.

Les drop-ins (`object-cache.php`, `advanced-cache.php`, `maintenance.php`, `db.php`) sont encore plus tôt. Un cache Redis légitime et un drop-in pirate peuvent cohabiter sous le même nom après un « nettoyage » trop pressé. Comparez à l'original du plugin de cache, octet par octet.

Si vous n'avez pas FTP, le gestionnaire de fichiers de l'hébergeur ou une archive du compte montre ces dossiers. Ne réinstallez pas WordPress par-dessus : les mu-plugins survivent. C'est le motif n°1 des « on a tout réinstallé, le cron est revenu ».

Cron système à côté de wp-cron

Sur un VPS ou un mutualisé un peu ouvert, crontab de l'utilisateur, cron cPanel, ou une tâche planifiée Plesk appelle un PHP à la racine toutes les cinq minutes. Ça n'apparaît pas dans `wp cron event list`. Demandez à l'hébergeur la liste des tâches du compte, ou lisez crontab si vous avez SSH.

Un `wget https://votre-site/wp-cron.php` légitime (recommandé quand `DISABLE_WP_CRON` est à true) n'est pas le problème. Un `php /home/xxx/public_html/radio.php` l'est. Notez la commande, ne la « mettez pas à jour ».

Après un incident mail, le cron système est souvent le canal qui relance `mail()` ou un SMTP volé. Croisez avec le journal d'envoi : heure du pic, heure de la tâche.

DISABLE_WP_CRON = true n'arrête pas crontab. Les deux files sont indépendantes.

Vider le transitoire sans se mentir

Beaucoup de tutos disent « videz le cron transitoire ». Il y a un transient lié à l'exécution, et l'option `cron` elle-même. Vider le premier sans retirer l'enregistreur donne une accalmie d'une page. Vider `cron` entier casse WooCommerce (actions planifiées de boutique), les publications programmées, les licences. Exportez, retirez les hooks que vous avez identifiés, pas la file entière.

Object cache (Redis, Memcached) peut resservir une vieille file. Videz-le après le correctif, comme après tout nettoyage. Sinon vous déboguez un fantôme. Voir aussi cache LiteSpeed qui ressert le malware.

Lire la file sans l'exécuter

Évitez de recharger massivement le site « pour voir si le cron part » : vous exécutez le malware. Travaillez en WP-CLI, ou sur une copie locale isolée (sans les URLs de callback). Si vous devez tester en production, coupez les sorties : firewall sortant, `mail()` désactivé le temps du dossier, SMTP révoqué.

Un hook qui fait un `wp_remote_get` vers un domaine inconnu est une exfiltration ou un C2. Notez l'URL, elle date et elle sert au constat. Ne la « pingez » pas depuis votre bureau pour voir : vous signalez que vous êtes dessus, et vous mélangez vos logs.

Refermer pour que rien ne se réarme

Entrée fermée (plugin vulnérable, admin fantôme, voisin), puis enregistreur retiré, puis file nettoyée, puis mots de passe panel / FTP / CMS, puis surveillance. Inverser remet une tâche neuve dans une file que vous venez de laver.

Régénérez les clés AUTH dans `wp-config` pour tuer les sessions. Vérifiez qu'aucun second site du compte n'écrit dans votre `wp-content`. Le cron d'un voisin est un classique sur mutualisé — WordPress propre, voisin sale.

  • Plus de hook inconnu dans `wp cron event list`.
  • mu-plugins et drop-ins alignés sur l'officiel.
  • crontab du compte lue et nommée.
  • Object cache et cache page vidés.

Surveillance les sept jours suivants

Une persistance mal retirée se montre en 24 à 72 h. Relistez les événements chaque matin, pas « quand ça ira mieux ». Un hook qui réapparaît avec le même nom et une `next run` trop tôt est le même enregistreur, pas un nouveau pirate.

Si vous déléguez, un prestataire sérieux fournit la liste des hooks retirés et le fichier qui les posait. « Cron nettoyé » sans ces deux lignes n'est pas un constat. Vous pouvez ouvrir un espace sans livrer d'accès tout de suite.

Questions fréquentes

Puis-je désactiver wp-cron pour arrêter le malware ?

+
Ça ralentit le déclenchement par visiteur, ça n'efface pas la file ni un cron système. Servez-vous-en comme mesure d'urgence courte, pas comme nettoyage. Trouvez l'enregistreur.

Un plugin « WP Crontrol » suffit-il ?

+
Il affiche et permet de supprimer un événement. Il ne lit pas les mu-plugins ni crontab. Si la tâche revient, l'outil n'est pas en cause : l'enregistreur est encore là.

Vider toute la table wp_options.cron est-il dangereux ?

+
Oui. Vous perdez publications programmées, files WooCommerce, licences, sauvegardes planifiées. Exportez, retirez les hooks identifiés, ou restaurez l'option depuis un dump propre lu ligne à ligne.

Le malware peut-il tourner si personne ne visite le site ?

+
Oui, si un cron système, un monitoring, un preload de cache ou un voisin appelle encore `wp-cron.php` ou un PHP isolé. Une boutique « sans trafic » n'est pas à l'arrêt.

Faut-il passer le vrai cron Unix à la place de wp-cron ?

+
C'est une bonne hygiène après incident (moins de pics, déclenchement prévisible), une fois le site propre. Le faire pendant l'attaque, en laissant le malware dans la file, exécute le payload plus régulièrement. Ordre : propre, puis cron système légitime.
À lire ensuite
Base WordPress injectée JavaScript dans wp_options Cache qui ressert le malware WP-CLI pour constater WordPress piraté Déclarer mon site