Des PHP dans wp-content/uploads : interdire l'exécution
Après avoir retiré les PHP de `wp-content/uploads`, un `.htaccess` (ou une règle Nginx) qui refuse l'exécution évite la récidive la plus simple. C'est le correctif que l'on pose le plus souvent — les médias restent, les scripts non.
Après avoir retiré les fichiers, un .htaccess dans uploads qui refuse le PHP évite la récidive la plus simple. C'est le correctif que l'on pose le plus souvent.
Pourquoi il n'y a rien à exécuter ici
WordPress y stocke des images, PDF, parfois des MP3. Aucun besoin de PHP. Un `index.php` vide « pour lister » n'est pas requis ; un `index.php` de 12 Ko l'est encore moins. Tout PHP ici est suspect jusqu'à preuve du contraire (et la preuve est rare).
C'est la rampe après CF7 upload, un plugin vulnérable, un File Manager, un thème nulled. CF7, File Manager.
Cherchez `*.php`, `*.phtml`, `*.php.jpg`, `.php.` , et les « images » dont les premiers octets sont `<?php`. Les `.htaccess` locaux qui font `AddType` / `SetHandler` sur les jpg sont aussi importants que les fichiers : ils transforment une photo en script. Ne videz pas les dossiers d'années : vous tuez le site visuel. Retirez les scripts, posez l'interdiction d'exécution, testez qu'un `test.php` déposé temporairement ne s'exécute plus.
Un `index.php` vide de 0 octet pour empêcher le listing n'est pas requis si le serveur a déjà `Options -Indexes`. Un index.php de 12 Ko l'est. Les « caches » d'optimisation qui posent du PHP ici se documentent ou se sortent. Le Deny doit les casser ou les lister comme exceptions assumées — pas les ignorer.
Trouver .php, .phtml, .php.jpg
Recherche FTP / `find` : `*.php`, `*.phtml`, `*.phar`, `*.php5`, `*.php.jpg`, `*.jpg.php`, fichiers commençant par un point `.php`. Les dates hors publications de médias. Les noms `cache`, `temp`, `x`, `wp-tmp`.
Un `.png` dont les premiers octets sont `<?php` : ce n'est pas une image. Téléchargez, ouvrez. Les « miniatures » WordPress légitimes sont des vrais JPEG.
Les noms `cache`, `temp`, `x`, `wp-tmp`, les fichiers commençant par un point. Un `.png` de 12 Ko qui commence par `<?php` n'est pas une miniature WordPress (celles-ci sont de vrais JPEG). Téléchargez, ouvrez, ne vous fiez pas à l'extension.
- Extensions PHP et doubles extensions.
- Fichiers cachés (point en tête).
- En-tête `<?php` dans un « média ».
Le .htaccess local qui force le contraire
Un `.htaccess` dans `uploads` ou un sous-dossier avec `AddType application/x-httpd-php .jpg` ou `SetHandler` : les images deviennent des scripts. Retirez ces règles. C'est aussi important que retirer le fichier.
Wordfence / iThemes peuvent écrire des règles. Relisez : gardez un Deny PHP, pas un AddHandler pirate.
Interdire l'exécution, pas vider le dossier
Apache : dans `uploads`, refuser le moteur PHP (`php_flag engine off` selon le setup, ou `Require all denied` sur `*.php`, ou `<FilesMatch>`). Testez qu'une URL `/wp-content/uploads/test.php` ne s'exécute plus (déposez un fichier temporaire puis retirez-le).
Ne videz pas les années `2020/` : vous tuez le site visuel. Retirez les scripts, gardez les médias. Supprimer malware.
Selon l'hébergeur : `php_flag engine off`, `FilesMatch` deny, ou une case « ne pas exécuter PHP dans uploads » chez o2switch / WP Cloud. Testez avec un fichier temporaire que vous retirez. Sans test, la règle est décorative.
Nginx, IIS, CDN
Nginx ignore le `.htaccess`. Règle `location ~* /wp-content/uploads/.*\.php` return 403. Chez o2switch / WP Cloud, un réglage « ne pas exécuter PHP dans uploads » existe parfois : cochez-le.
Un CDN qui sert le HTML/PHP depuis l'origine : le Deny doit être à l'origine. Un CDN « cache everything » peut resservir un vieux PHP ; purge.
Double extension et « image » qui commence par php
Apache négocie parfois `.php.jpg` comme PHP. D'où l'interdiction large, pas seulement `*.php`. Les MIME envoyés par le navigateur à l'upload mentent ; le serveur doit filtrer l'extension réelle.
Formulaires et File Manager
Coupez les uploads de formulaires inutiles. Désinstallez File Manager après usage. Sinon le `.htaccess` sera contourné par un admin qui dépose à la racine. La règle uploads n'est pas un pare-feu global.
Coupez les uploads de formulaires inutiles. Désinstallez File Manager après usage. Sinon quelqu'un dépose à la racine et votre .htaccess uploads ne sert à rien. File Manager.
Contrôle à J+7
Recherche PHP à nouveau. Dates. Un mu-plugin peut réécrire un PHP dans uploads. Backdoor.
Nginx ignore le `.htaccess` : règle `location` dédiée. Un CDN cache everything ressert un vieux PHP : purge. La règle uploads n'empêche pas un File Manager de déposer à la racine. Contrôle à J+7 : un mu-plugin peut réécrire un PHP ici. Backdoor PHP. Créer un espace.
J+7 sans nouveau PHP + probe toujours 403 : la règle tient. Un nouveau `.php.jpg` : mu-plugin ou formulaire encore ouvert. CF7, File Manager.
Plugins qui écrivent du PHP ici, et les PDF
Quelques caches / builders posent du PHP dans uploads : mauvaise pratique. Sortez-les du web ou changez de plugin. En attendant, documentez-les : un Deny trop large les cassera, un Deny trop étroit laissera le `.php.jpg`. Les PDF peuvent être dangereux pour le poste qui les ouvre ; pour le serveur, l'urgence reste l'exécution PHP.
Ne bloquez pas tout `wp-content` en PHP : thèmes et plugins en ont besoin. `uploads`, `upgrade`, dossiers backup. Multisite : chaque `sites/ID`. Multisite.
Wordfence qui « répare » un jpg : vérifiez que ce n'était pas une photo. Comparez au backup. Créer un espace.
Tester que la règle tient vraiment
Déposez `uploads/__probe.php` qui fait `<?php echo 'PROBE';`, appelez l'URL : vous devez voir le source ou un 403, pas PROBE. Effacez le probe. Refaites après une MAJ Wordfence / Solid qui réécrit les .htaccess. Chez Nginx, le .htaccess n'a jamais existé : la location doit être dans le vhost.
J+7 : find des PHP à nouveau. Un mu-plugin réécrit. Backdoor PHP. Créer un espace.