Code en base64 dans des fichiers PHP : démêler légitime et porte
Certains plugins encodent pour se protéger. Un `eval(base64_decode(...))` isolé dans un fichier anodin, presque jamais. Le contexte décide : header de plugin connu, ou une ligne collée en bas de `wp-blog-header.php`. On ne « décode pas tout Internet » ; on tranche fichier par fichier.
Certains plugins encodent pour se protéger. Un eval(base64_decode(...)) isolé dans un fichier anodin ne l'est presque jamais. Le contexte décide.
Ce que base64 veut dire ici
Base64 n'est pas un malware. C'est un alphabet pour porter des octets dans du texte. Une image dans un CSS, une clé, un chunk Elementor : plein de base64 honnêtes. Le problème, c'est PHP qui décode puis `eval` / `assert` / `preg_replace` /e / `create_function` / `include` d'une chaîne.
Les kits l'utilisent pour passer les scans à mots-clés (`eval` seul se voit ; une chaîne de 4 Ko moins). Ce n'est pas sophistiqué. C'est fréquent.
Un fichier « tout en une ligne » de 80 Ko de caractères A-Za-z0-9+/= dans un `radio.php` : vous n'avez pas besoin de tout lire. Classez porte, copiez, sortez. Le critère n'est pas « il y a du base64 quelque part » (Gutenberg, CSS, e-mails en ont). C'est PHP qui décode puis exécute. Un `base64_decode` qui alimente un `echo` d'image n'est pas le même dossier qu'un `eval`.
Légitime : licences, builders, ionCube
Plugins payants : chargeur ionCube / sourceguardian, ou un `eval` de licence (discutable, mais connu, header Plugin Name, auteur). Thèmes ThemeForest : parfois un `class` encodé. Comparez au zip que vous avez acheté. S'il est identique, ce n'est pas l'incident — c'est une hygiène douteuse, un autre débat.
Elementor, Divi : du base64 dans le CSS et les data. Ce n'est pas dans un `index.php` cœur. Le chemin du fichier oriente autant que la fonction.
WordPress core n'embarque pas d'`eval(base64_decode` dans `wp-load.php`. Si vous le voyez là, ce n'est pas « une MAJ ». C'est une greffe.
- Zip d'origine du plugin / thème = même blob ?
- Fichier cœur CMS = jamais légitime.
- Fichier anodin à la racine = presque jamais.
Le motif `eval` + decode qui tranche
`eval(base64_decode(`, `eval(gzinflate(base64_decode(`, `eval(str_rot13`, doubles et triples enveloppes. Variantes : `$a='base'.'64_decode'; $a(...)`. Si vous le voyez dans `footer.php` après le HTML, ce n'est pas une licence Divi.
`assert`, `system`, `passthru`, `shell_exec` à côté : webshell, pas encodeur de thème. Voir C99 WSO.
Un `base64_decode` qui alimente `mail()` ou `file_put_contents` : même gravité, autre symptôme (spam, réécriture).
Où chercher sans noyer le diff
Fichiers modifiés récemment, racine, `mu-plugins`, drop-ins, thème enfant, `uploads` PHP, `.htaccess` prepend. Pas un grep de tout `wp-includes` d'abord : trop de bruit si vous cherchez juste `base64` (le core en a pour de vraies raisons, rares).
Grep ciblé : `eval(base64` et `gzinflate` et `str_rot13` dans `wp-content` et la racine. Puis les cœurs si le poids des fichiers a bougé. Voir index lourd.
La base : `wp_options` et `post_content` peuvent contenir le même motif. Un grep SQL sur `base64_decode` dans `wp_posts` sort des faux positifs (tutos) et de vrais payloads. Lisez le contexte de la ligne. Un article de 2019 qui cite le mot en clair n'est pas une injection. Un `wp_options` `widget_text` d'une ligne encodée, créé cette nuit, si. La date `option_id` / `post_modified` tranche plus vite qu'un décodeur.
Décoder pour classer, pas pour exécuter
Un décodeur en ligne ou `base64 -d` sur un extrait : vous lisez des noms de domaines, des `mail(`, des `wp_insert_post`. Ça classe. N'en faites pas un `php -r eval`.
Les triples enveloppes : deux, trois passes. Si au bout vous avez encore du bruit, arrêtez-vous : c'est une porte, le détail est pour le constat, pas pour la curiosité.
Ne collez pas un payload entier dans un chatbot public. Extraire le domaine de sortie suffit au ticket.
Faux positifs : Gutenberg, CSS, e-mails
Les articles qui expliquent la sécurité citent `base64_decode` en clair. Vos propres tutos. Les newsletters HTML. Un grep sans lire classe « infecté » un blog légitime. Le fichier et le `eval` manquant tranchent.
Les SVG et les data-uri. Les exports ACF. Bruit.
Un plugin de sécurité qui « encode » ses règles : lisez le header. En doute, isolez, testez, comparez au zip wordpress.org.
Une ligne dans un cœur CMS
Remplacez le fichier par le zip officiel après copie. Ne « nettoyez » pas la ligne à la main en laissant un caractère minifié : vous cassez le parse et vous n'avez plus de preuve propre.
Si dix fichiers cœur ont une ligne ajoutée, quelqu'un a un process (cron, shell). Un seul fichier : parfois un edit manuel (éditeur WP). Cherchez quand même l'entrée.
Les checksums WordPress (un outil qui compare au repo) accélèrent. Ils ne voient pas `wp-content`.
Après retrait : la copie encodée ailleurs
Le même blob dans un mu-plugin et en fin de `functions.php` : deux amorces. Grep le début de la chaîne (20 caractères) sur tout le compte.
Un cron recolle le blob. J+1. Panel.
Search Console : le HTML spam peut venir de ce PHP. Inspection live après retrait. Safe Browsing seulement si ressource dangereuse ; le délai 24-72 h n'attend pas que vous ayez « tout décodé ». Grep les vingt premiers caractères du blob sur tout le compte : la même enveloppe dans un mu-plugin et en fin de `functions.php`, deux amorces. En retirer une seule, le title japonais revient.
Scanners et ce qu'ils ratent
Wordfence flag beaucoup d'`eval`. Utile comme liste. « Réparer » un plugin payant encodé le casse. « Ignorer » un `index.php` cœur, erreur. Traitez la liste à la main, fichier par fichier.
Un one-liner `eval($x)` avec `$x` construit sans le mot base64 : faux négatif. Les dates et les poids restent vos filets.
Vous pouvez déclarer le site. Une liste de chemins `eval(base64` suffit à démarrer ; pas besoin d'avoir tout décodé.
- Contexte (cœur vs plugin payant vs racine).
- Copie puis retrait / overlay zip.
- Grep du blob sur le compte.
- J+1 contre réécriture.