Durcissement WordPress : empêcher l’exécution de PHP dans uploads

Dans la plupart des intrusions visant WordPress, le point de bascule n’est pas forcément le cœur du CMS. C’est souvent la manière dont le serveur traite le contenu déposé dans le répertoire uploads. Si quelqu’un réussit à faire passer un fichier PHP (ou un fichier déguisé) dans cet endroit, l’impact peut devenir immédiat: exécution de code, prise de session, modification de fichiers, et parfois pivot vers d’autres machines.

Le durcissement WordPress sur ce sujet a un objectif simple à formuler et difficile à obtenir de façon fiable partout: empêcher l’exécution de PHP dans le dossier wp-content/uploads, même si quelqu’un y arrive avec un fichier indésirable. La bonne nouvelle, c’est que ce n’est pas “une” configuration magique, mais un empilement de contrôles que l’on peut rendre robuste.

Pourquoi uploads attire les problèmes

WordPress est conçu pour stocker des médias et des fichiers générés par l’application dans wp-content/uploads. On y met des images (JPG, PNG, WebP), des PDF, des vidéos, et parfois des fichiers fournis par des plugins: documents, exports, sauvegardes.

Le piège, c’est que le serveur web ne connaît pas l’intention. Il sait juste “ce que le type de ressource ressemble à ça, et comment on mappe l’URL vers un handler”. Si, pour une raison ou une autre, votre configuration PHP fait exécuter *.php (ou certains variantes) dans uploads, un dépôt malveillant peut devenir exécutable.

Même sans accès direct au serveur, des attaquants peuvent obtenir un chemin d’entrée via:

    une faille dans un plugin, une mauvaise validation lors de l’upload, une fonctionnalité de transfert de fichiers, des accès volés (compte admin, compte auteur, ou session hijack), ou encore une chaîne d’actions où une vulnérabilité permet de déposer et d’appeler un fichier.

D’où l’idée de base: même si quelqu’un parvient à déposer un fichier suspect dans uploads, la configuration doit empêcher l’exécution.

Le bon principe de sécurité: bloquer côté serveur, pas seulement dans WordPress

On pourrait être tenté de “durcir” via WordPress: empêcher certains types de fichiers, filtrer les extensions, refuser l’upload de PHP. Le problème, c’est que WordPress n’est pas un pare-feu universel. Il dépend des plugins, des réglages, et du comportement exact du serveur.

Le durcissement le plus solide consiste à appliquer une politique côté serveur web et interpréteur. Concrètement:

    le serveur doit servir les fichiers statiquement (images, PDF, etc.), et il ne doit pas déclencher le handler PHP pour ceux placés dans uploads.

Cela réduit la surface d’exécution, même si un fichier passe entre les mailles ailleurs.

Commencer par comprendre votre pile (Apache ou Nginx, PHP-FPM ou module)

Avant de toucher à quoi que ce soit, prenez deux minutes pour vérifier le contexte. Les solutions existent dans des variantes selon l’environnement:

    Apache avec PHP en mode module: php est traité via le module Apache (souvent libapache2-mod-php). Apache avec PHP-FPM: le mapping se fait via un proxy fastcgi, et les mécanismes de blocage passent par le matching vers le handler. Nginx avec PHP-FPM: c’est très courant en mutualisé moderne, et le blocage s’écrit dans la config server et location. Panneaux d’hébergement: cPanel, Plesk, ou des couches “anti-attaque” peuvent déjà imposer des restrictions ou, au contraire, nécessitent une approche adaptée. Systèmes “intelligents”: certains modules WAF, caches, ou plateformes gèrent autrement les requêtes.

Si vous appliquez une règle .htaccess sur un serveur Nginx, elle restera inutile. Inversement, une règle Nginx ne suffira pas si Apache exécute réellement PHP ailleurs. Le but est d’attaquer le bon point de contrôle.

Méthode Apache: empêcher l’exécution PHP dans wp-content/uploads avec htaccess

Sur Apache, la méthode la plus répandue consiste à ajouter une règle dans un fichier .htaccess placé dans le répertoire wp-content/uploads (ou un sous-répertoire ciblé). L’idée est de forcer le serveur à refuser le traitement par PHP, et si possible à refuser l’accès à certaines extensions.

Il y a une nuance importante: selon la façon dont PHP est configuré sur votre serveur, bloquer une extension précise peut ne pas suffire. Par exemple, certains systèmes autorisent l’exécution via des variantes comme file.php.jpg si le mapping du handler n’est pas strict. D’autres scénarios reposent sur l’ordre de traitement et sur les règles de réécriture.

image

Une approche pragmatique consiste à: 1) refuser l’accès aux fichiers dont l’extension ressemble à PHP, 2) et s’assurer que le serveur ne “rewrapp” pas ces fichiers vers le handler.

Voici un exemple de règles à placer dans wp-content/uploads/.htaccess (à adapter selon votre environnement):

    Activer un bloc “deny” sur les extensions PHP et variantes fréquentes. Désactiver l’exécution via une règle qui empêche le handler PHP.

Exemple typique (à adapter avec prudence, car certaines lignes peuvent dépendre des modules activés):

Options -Indexes Require all denied Require all denied

Pourquoi deux lignes ? Parce que certaines tentatives contournent l’extension “fin de chaîne” en jouant avec des suffixes, ou avec des formats que le serveur associe à l’exécution. La deuxième variante vise les cas où l’extension PHP ne se termine pas strictement à la fin du nom, ce qui peut être interprété différemment selon la pile.

Si votre hébergeur utilise des règles internes, ou si PHP est déclenché via un autre mécanisme (type SetHandler), la politique “deny l’accès” est souvent plus robuste que “forcer le handler à ne pas s’activer”. L’accès refusé a un effet net: même si une requête arrive, elle ne sera pas servie ni exécutée.

Cas particuliers sur Apache

    Le support .htaccess est parfois désactivé (AllowOverride None). Dans ce cas, vos règles ne s’appliqueront pas. Il faut soit demander l’activation à l’hébergeur, soit appliquer la règle dans la configuration du vhost (ce qui est encore mieux). Les images ne doivent pas être cassées. Avec Options -Indexes, vous évitez juste l’affichage d’une liste de fichiers si quelqu’un ouvre le dossier. Les règles FilesMatch ciblent uniquement les noms correspondant à des extensions. Les plugins peuvent stocker du PHP dans uploads si vous avez un “fonctionnement particulier” (par exemple un outil qui dépose des fichiers exécutables, ou un répertoire de cache mal placé). Dans ce cas, le blocage empêche l’exécution, mais aussi parfois certaines fonctionnalités attendues. C’est rare, mais il faut vérifier. Nettoyage et maintenance: si un fichier malveillant est déjà présent dans uploads, ces règles empêchent l’exécution future, mais ne suppriment pas ce qu’il y a. Il faut ensuite nettoyer.

Méthode Nginx: bloquer l’accès ou l’exécution dans uploads

Sur Nginx, l’idée se traduit différemment. Nginx exécute PHP via une règle location qui envoie les requêtes vers php-fpm. Pour empêcher l’exécution dans uploads, il faut soit:

    faire en sorte que les requêtes vers uploads ne correspondent jamais au location ~ \.php$, ou écrire une règle plus spécifique avant celle qui gère PHP, qui renvoie une erreur ou refuse l’accès.

Le schéma général, c’est d’ajouter un location prioritaire pour wp-content/uploads/ et d’y bloquer les extensions PHP.

L’exemple conceptuel ressemble à ceci:

    un bloc location ~* ^/wp-content/uploads/.*\.php$ qui renvoie 403, ou un bloc location ^~ /wp-content/uploads/ où l’on neutralise la correspondance avec .php.

Concrètement, je recommande de ne pas inventer ici des configurations exactes pour votre stack, car le “bon” emplacement des règles dépend du vhost et du chemin d’entrée. Mais le principe est constant: une règle spécifique à uploads doit primer sur le handler PHP.

Piège courant sur Nginx: ordre et regex

Avec Nginx, l’ordre et la priorité des location comptent. Si la règle qui envoie vers php-fpm est plus “forte”, vos restrictions peuvent ne pas s’appliquer. Dans des environnements réels, j’ai vu des serveurs où la règle de blocage existait “dans la config”, mais placée après une directive location ~ \.php$ qui interceptait tout.

Donc, testez. Ne vous contentez pas de “penser que c’est bon”.

Vérifier ce que WordPress et vos plugins font réellement

Le durcissement WordPress ne consiste pas uniquement à bloquer ce qui ressemble à du PHP. Il faut aussi anticiper ce qui se passe côté application:

    WordPress sert le contenu des uploads comme des fichiers statiques via le serveur web. Une partie des plugins peut créer des fichiers dans uploads (logs, backups, caches, exports). Certains plugins de sécurité déplacent ou réécrivent des flux, et peuvent affecter les chemins d’accès. Un thème ou un plugin peut créer des scripts dans uploads (je déconseille, mais c’est vu dans des projets bricolés).

Avant de figer une politique stricte, regardez ce qui existe dans uploads. Si vous voyez des .php, .phtml, ou des fichiers suspects, ne posez pas la règle seulement comme une mesure “préventive”. Posez-la, puis traitez la présence: suppression, nettoyage, et audit.

Tester le durcissement sans casser votre site

Une fois la règle en place, vous voulez deux résultats: 1) les fichiers légitimes sont servis, 2) les tentatives d’exécution dans uploads échouent.

Le test le plus simple consiste à vérifier le comportement HTTP de requêtes sur des noms ciblés. L’idée n’est pas de laisser traîner un fichier d’essai en production, mais de contrôler le flux.

Un mini plan de test (rapide et réaliste)

    Recherchez s’il existe déjà des fichiers .php dans wp-content/uploads. Créez un fichier de test uniquement si vous avez un moyen de l’effacer ensuite, idéalement dans un environnement de staging ou via un dossier dédié. Essayez de charger ce fichier via son URL complète et observez le code HTTP. Consultez les logs Apache ou Nginx, au moment du test, pour confirmer que la requête est interceptée par la règle attendue.

Si vous faites ça en production, faites preuve de discipline. Un test propre vaut mieux qu’une “découverte” plus tard quand un incident arrive.

En pratique, l’objectif n’est pas forcément de renvoyer exactement un seul code, mais d’avoir un refus net. Typiquement, un 403 Forbidden ou un 404 Not Found est acceptable. Ce que vous voulez éviter, c’est un 200 OK avec du contenu généré par PHP.

Erreurs fréquentes et ajustements qui font la différence

Le durcissement échoue rarement par manque d’intention. Il échoue plutôt à cause de détails.

Voici les problèmes que je rencontre le plus souvent, et la manière dont on les corrige.

1) Bloquer l’extension finale, mais pas les contournements

Si votre règle ne refuse que *.php, un attaquant peut parfois viser shell.php.txt, ou un nom qui “ressemble” à une exécution selon le mapping du serveur. Même si ce n’est pas systématique, c’est un axe réaliste.

Ajustement: bloquer un ensemble d’expressions raisonnable, pas uniquement l’extension exacte. Sans tomber dans la sur-blindage qui casse d’autres usages.

image

2) Confusion entre “exécution” et “accès”

Bloquer l’exécution via un mauvais FilesMatch peut ne rien faire si, au final, le serveur sert le fichier ou le route vers un autre handler. Inversement, si vous refusez l’accès de façon claire, le risque d’exécution disparaît.

Ajustement: quand vous pouvez, préférez le refus d’accès aux fichiers suspects dans uploads, plutôt qu’un contournement “technique”.

3) Le serveur exécute PHP ailleurs, pas seulement dans uploads

Certaines installations exécutent PHP via d’autres chemins, comme un répertoire de cache, un dossier temporaire, ou un emplacement lié à un plugin. Votre règle dans uploads ne suffit donc pas si l’attaque passe par un autre endroit.

Ajustement: considérez uploads comme un verrou important, mais pas le seul.

4) Règles écrasées par des couches du fournisseur

Sur des hébergements gérés, les fichiers .htaccess peuvent être ignorés, ou une couche de reverse proxy peut modifier le traitement. Dans ce cas, la règle “n’existe pas” pour le trafic réel.

Ajustement: vérifiez dans les logs, et, si https://gardewp.fr/securite-wordpress/ possible, confirmez via tests HTTP. Si ça ne répond pas, il faut changer de méthode, ou demander une modification au support.

Un cas réel qui aide à cadrer le risque

J’ai vu une configuration où uploads semblait “propre”. Les scans ne montraient rien, et WordPress était à jour. Pourtant, des requêtes avaient déjà été envoyées vers des endpoints typiques de dépôt et d’exécution. La règle .htaccess de uploads n’était pas présente au bon endroit, et surtout, .htaccess était ignoré.

Le site n’avait pas été “pwned” au moment du scan, mais les traces montraient l’intention et la capacité. Quand on a corrigé l’application réelle des règles côté serveur, les tentatives suivantes ont cessé de produire des erreurs différentes, et surtout, les requêtes vers .php dans uploads ont été rejetées proprement.

Le point à retenir: un durcissement utile se mesure. On ne se contente pas de l’écrire, on valide qu’il s’applique.

Mettre en place ce durcissement sans faire de dégâts

Le plus gros risque en durcissement, ce n’est pas la règle elle-même. C’est la confiance excessive dans une règle non testée, ou trop agressive.

Voici les décisions à prendre en amont, plutôt que pendant une urgence.

1) Ciblez le bon répertoire: wp-content/uploads uniquement, plutôt que “tout le site”. 2) Gardez l’impact sur les médias: n’ouvrez pas la porte aux erreurs de correspondance qui pourraient casser une URL d’image. 3) Traitez ce qui est déjà suspect: bloquer ne remplace pas le nettoyage. 4) Préparez un plan de retour arrière: sauvegarde de la config, possibilité de retirer rapidement.

Dans mon expérience, une petite procédure interne (même une note rapide) évite les frictions quand on doit changer vite. Vous gagnez du temps le jour où il faut prouver que la règle a bien été appliquée et que le comportement attendu est atteint.

Au-delà de uploads: quelques contrôles complémentaires utiles

Même si votre objectif est précis, le durcissement WordPress forme un ensemble. Bloquer l’exécution dans uploads réduit énormément le risque “dépose et exécute”, mais vous pouvez le renforcer avec d’autres barrières, sans transformer votre site en chantier.

On peut penser à:

    limiter l’upload aux rôles nécessaires, vérifier la politique de types de fichiers autorisés, durcir le thème et les plugins, garder des versions cohérentes, surveiller les fichiers nouvellement créés dans uploads, et s’assurer que les permissions de fichiers ne permettent pas à l’application d’écrire là où elle ne devrait pas.

Je garde ces points en tête parce que la réalité, c’est que l’attaque finit rarement “dans uploads” par hasard. Elle arrive souvent via un autre maillon, et si ce maillon reste faible, vous devrez recommencer.

Checklist de mise en production (sans surenchère)

    Vérifier votre serveur (Apache ou Nginx) et le mode PHP, avant d’écrire une règle. Ajouter la règle au bon emplacement, pour que le trafic réel l’applique. Faire un test simple d’accès à un fichier .php fictif, ou via une méthode sûre en staging. Surveiller les logs au moment du test pour confirmer l’interception. Nettoyer et auditer les fichiers déjà présents si vous trouvez des anomalies dans uploads.

Cette séquence évite le classique “on a ajouté une règle” mais elle n’est jamais prise en compte.

Conclusion opérationnelle: le verrou uploads est un accélérateur de sécurité

Empêcher l’exécution de PHP dans wp-content/uploads est l’une de ces mesures qui ne fait pas de bruit, mais qui change la forme du risque. Au lieu de laisser un dépôt malveillant devenir immédiatement exécutable, vous cassez la chaîne à un endroit très pratique pour les attaquants.

Le durcissement WordPress le plus efficace, c’est celui qui est appliqué réellement par le serveur, validé par des tests, et maintenu dans le temps même après des changements d’hébergement ou de configuration. Une règle bien placée dans Apache ou une location correcte sur Nginx peut représenter une différence énorme entre “tentative bloquée” et “incident complet”.

Si vous voulez que je vous aide à formuler la règle exacte, dites-moi simplement votre serveur (Apache ou Nginx), le mode PHP (module ou PHP-FPM si vous le savez), et la structure de chemin utilisée sur votre site. Je pourrai vous proposer une version adaptée, sans sur-ciblage.