Renforcer sécurité WordPress : bloquer les fichiers dangereux dans uploads

Les attaques qui visent WordPress ne cherchent pas toujours la faille “magique” dans le noyau. Très souvent, elles s’accrochent à des angles plus prosaïques, comme l’endroit où l’on dépose des fichiers: le dossier uploads. C’est là que vivent vos images, vos PDFs, vos thèmes et parfois des scripts oubliés. Et c’est aussi là que certains attaquants déposent des charge utiles, puis tentent de les exécuter.

Renforcer sécurité WordPress passe donc par un principe simple: autoriser le téléchargement et l’affichage des fichiers légitimes, empêcher l’exécution côté serveur. Dans la pratique, cela demande un peu de méthode, parce que les configurations varient selon que vous êtes sous Apache ou Nginx, et selon vos usages (multisite, formats spécifiques, options d’hébergement). Je vous détaille une approche réaliste, avec des garde-fous et des cas limites.

Pourquoi le dossier uploads devient une cible

Le dossier wp-content/uploads est populaire pour plusieurs raisons.

D’abord, il est “prévu” pour recevoir des contenus depuis l’interface d’administration. Ensuite, ses chemins et ses formats sont prévisibles. Enfin, beaucoup de sites laissent des permissions et des règles trop ouvertes, surtout après des migrations, des mises à jour manuelles ou des changements de serveur.

Quand un attaquant réussit à déposer un fichier dans uploads, il ne cherche pas forcément à exploiter une vulnérabilité distante. Il essaye plutôt une stratégie de type: “je place mon fichier ici, et je le fais exécuter”. Sur https://gardewp.fr/securite-wordpress/ un site mal configuré, un fichier avec une extension piégée peut déclencher une exécution interprétée par PHP (ou par d’autres mécanismes selon la stack).

Même sans certitude sur chaque mécanisme exact, le signal reste le même: si votre serveur autorise l’exécution de scripts dans uploads, vous lui donnez une porte d’entrée.

Le point de départ: distinguer “bloquer” de “désactiver l’exécution”

Il y a deux objectifs qui se ressemblent mais ne sont pas identiques.

Le premier est de bloquer l’accès à certains fichiers. Le second est de désactiver l’exécution de tout script dans uploads, tout en laissant les médias fonctionner.

Dans la plupart des cas, la meilleure approche est le second objectif. Une liste noire stricte peut rater des variations. Par contre, empêcher l’exécution à cet endroit coupe la chaîne d’un grand nombre d’attaques, y compris quand l’extension n’est pas celle que vous auriez imaginée.

Cela dit, vous pouvez garder une logique de défense en profondeur: bloquer les extensions manifestement dangereuses et, surtout, empêcher l’exécution PHP (et parfois d’autres interprétations) dans ce répertoire.

Apache: la règle qui protège réellement uploads

Sur Apache, la manière classique de protéger wp-content/uploads consiste à ajouter des directives dans un fichier .htaccess ou dans la configuration vhost. L’idée est de forcer Apache à ne pas exécuter les scripts dans ce dossier.

Concrètement, on utilise des directives du type php_flag et php_admin_flag si PHP est en mode module, ou bien SetHandler et des restrictions selon votre configuration. Beaucoup d’hébergeurs modernes utilisent PHP-FPM, et dans ce cas les leviers diffèrent.

Voici un modèle courant, à adapter. Vous le mettez généralement dans un .htaccess placé à l’endroit où Apache l’applique, souvent dans wp-content/uploads ou au moins dans wp-content avec un bloc ciblé.

Require all denied Php_flag engine off Options -Indexes

Quelques remarques tirées de situations réelles:

    Le php_flag engine off ne marche que dans certains contextes. Si PHP est géré par PHP-FPM, Apache n’a pas la main via mod_php.c, et la directive peut être ignorée. Dans ce cas, il faut agir via FilesMatch et surtout via la configuration PHP côté serveur. Options -Indexes évite que le serveur liste les fichiers si un répertoire est exposé. Ce n’est pas une barrière contre l’exécution, mais ça réduit la surface. La liste des extensions est à prendre comme un point de départ. Dans l’ombre, certains attaquants jouent avec des extensions rares ou des double extensions. Vous pouvez renforcer en ajoutant d’autres motifs, mais l’essentiel reste l’empêchement d’interprétation.

Si vous n’êtes pas sûr du mode PHP, regardez comment PHP est servi (dans l’onglet serveur de votre hébergeur, ou via un test contrôlé). Sur un site déjà en production, évitez les tests destructifs. Un audit de configuration est souvent plus rapide que du “guess”.

Cas limite: si vous utilisez des fichiers “bizarres” dans uploads

J’ai vu des sites où uploads servait aussi à stocker des fichiers de travail, parfois des .phtml pour une fonctionnalité vieillotte, ou des fichiers générés dynamiquement que l’équipe pensait ne jamais exécuter. Si vous bloquez trop agressivement, vous casserez un flux légitime.

La bonne pratique consiste à:

    vérifier le catalogue d’extensions réellement présentes dans wp-content/uploads, bloquer l’exécution, puis éventuellement durcir les accès à certaines extensions, surveiller les erreurs après déploiement.

Une fois, sur une boutique WordPress, le blocage d’une extension “rare” a cassé le rendu d’un document transformé à la volée via un chemin détourné. Le correctif a été de limiter strictement aux scripts, plutôt que de refuser l’accès à des types de fichiers “non exécutés”.

Nginx: le piège des lieux où PHP peut s’accrocher

Sous Nginx, la protection passe souvent par le fait que les requêtes vers wp-content/uploads ne doivent pas être redirigées vers un handler PHP. Dans une config Nginx standard WordPress avec PHP-FPM, on trouve généralement des blocs location ~ \.php$ qui envoient les fichiers .php à PHP-FPM.

La défense consiste à introduire une règle plus spécifique pour les uploads, ou à exclure ce chemin de la règle générale.

Exemple conceptuel (à adapter à votre fichier de conf):

Location ~* ^/wp-content/uploads/.*\.php$ Deny all; Location ~ \.php$ Include snippets/fastcgi-php.conf; Fastcgi_pass unix:/run/php/php-fpm.sock;

Le détail important ici est l’ordre de priorité. Dans Nginx, la façon dont les expressions régulières et les location sont évaluées peut surprendre. Ce n’est pas un sujet à bricoler au hasard si vous ne maîtrisez pas votre config. Le bon réflexe reste: tester sur staging, puis valider sur production avec un chemin témoin.

Si vous ne voulez pas refuser toutes les requêtes .php dans uploads, vous pouvez aussi simplement neutraliser l’exécution à cet endroit. Mais en pratique, un deny all sur les scripts dans uploads est souvent le plus clair.

Cas limite: chemins personnalisés et multisite

Sur un multisite, vos uploads peuvent vivre sous des sous-dossiers spécifiques (par exemple uploads/sites/id). La règle doit couvrir ces variantes.

J’ai déjà vu des règles trop strictes, ciblées sur wp-content/uploads/ seulement, et qui laissaient passer un sous-chemin du multisite. Résultat: les protections étaient “en apparence” actives, mais une portion restait vulnérable.

Ne pas compter uniquement sur .htaccess ou Nginx

Même si vous faites le job proprement à la couche web, la sécurité ne s’arrête pas là. Deux raisons:

Une règle mal déployée peut ne pas s’appliquer (cache, mauvaise cible, permission empêchant l’écriture du .htaccess). Les attaquants ne cherchent pas seulement l’exécution, ils cherchent aussi à laisser des traces exploitables, par exemple en déposant des fichiers au bon endroit puis en profitant d’une autre faiblesse (compte compromis, plugins obsolètes, mauvaise gestion des autorisations).

C’est pour cela qu’une stratégie saine combine le blocage d’exécution avec une hygiène d’upload.

Réduire le risque à l’upload dans WordPress

WordPress fournit des mécanismes de base, mais ils ne remplacent pas la configuration serveur.

Au niveau WordPress, vous pouvez agir sur deux axes:

    Limiter les extensions autorisées dans le média uploader. Empêcher l’ajout de fichiers suspects, y compris ceux qui “passent” la détection MIME.

La réalité opérationnelle: la détection MIME et l’analyse de contenu ne sont jamais parfaites sans outil dédié. Un fichier peut porter une extension “inoffensive” et contenir autre chose. D’où l’intérêt de verrouiller l’exécution côté serveur, même si vous faites aussi du filtrage applicatif.

Une approche pragmatique: liste d’extensions utiles

Sur la plupart des sites, les seuls formats réellement utilisés dans uploads sont des images, des documents PDF et quelques archives. En durcissant la liste autorisée, vous réduisez la probabilité qu’un fichier exécutable se retrouve au bon endroit.

Au passage, vérifiez aussi les fichiers de type image qui peuvent contenir du code dans des formats mal traités, même si la majorité des serveurs et navigateurs filtrent. Le risque est rare comparé à l’exécution PHP, mais le filtrage réduit la surface.

Je préfère une logique “allow list” plutôt qu’une “block list” quand on a une idée claire des besoins.

Détection et gestion des fichiers déjà présents

Bloquer l’exécution dans uploads empêche l’avenir, mais il faut aussi gérer le passé. Sur un incident, vous voulez savoir ce qui traîne encore.

Une méthode simple, sans fantasmer sur la perfection:

image

Lister les fichiers inhabituels dans wp-content/uploads, Vérifier leur date de modification, leur taille et leur propriétaire, Contrôler le contenu si c’est un fichier de script ou un fichier binaire suspect, Supprimer immédiatement ce qui est clairement malveillant, puis corriger la cause (compte compromis, plugin vulnérable, mot de passe faible).

Le piège, c’est de supprimer sans corriger. Vous pouvez bloquer l’exécution et croire que tout est réglé, mais si votre compte administrateur reste compromis, l’attaquant peut retenter et déposer un autre fichier, voire exploiter une autre zone.

Quoi regarder en priorité

Sur un site WordPress infecté, j’ai souvent vu revenir:

    des fichiers PHP “cachés” dans des dossiers d’uploads, des fichiers avec des noms aléatoires et des dates très proches du moment de l’infection, des fichiers de petite taille (moins de quelques kilo-octets) qui ressemblent à des scripts, même si leur extension varie.

Sans donner de recette “magique”, la démarche consiste à trier par anomalies.

Checklist courte avant de déployer le durcissement

Avant de pousser des règles serveur, faites un petit verrouillage de workflow. Pas besoin de cérémonial, mais évitez le déploiement à l’aveugle.

    Faire un test sur un environnement de préproduction identique (même type de PHP, même Nginx ou Apache). Vérifier le mode PHP (mod_php ou PHP-FPM) pour choisir les bonnes directives. Couvrir les chemins réels de uploads (cas multisite inclus). Tester l’affichage de médias (images, PDF) après activation. Prévoir un accès de secours au serveur, au cas où la config casserait le rendu.

C’est souvent dans le détail d’hébergement que ça déraille, pas dans l’idée générale.

Ajuster finement, sans casser votre site

Le blocage d’exécution peut avoir des effets secondaires. Les plus fréquents sont:

    des plugins qui génèrent des contenus dans uploads sous forme de fichiers exécutables (approche rare, mais je l’ai vue), des transformations qui reposent sur un script exécuté depuis un chemin inhabituel, des dépendances héritées après une migration.

Une règle trop “optimiste” basée sur une seule extension peut suffire à bloquer un cas légitime. L’équilibre consiste à empêcher l’exécution côté serveur, et à garder un filtrage applicatif raisonnable au niveau WordPress.

image

Si vous devez autoriser un format particulier, faites-le sur le principe du besoin réel, pas sur l’espoir que “ça marchera quand même”.

Durcir davantage: permissions, ownership et traçabilité

Ces mesures ne remplacent pas la règle web, mais elles complètent le tableau.

    Assurez-vous que les permissions sur wp-content/uploads ne laissent pas un mode d’écriture trop large. Vérifiez le propriétaire des fichiers. Sur certains environnements, des fichiers peuvent être laissés appartenant à un autre utilisateur, ce qui complique la remédiation. Activez une journalisation exploitable, au moins au niveau d’erreurs web (et si possible dans le système, selon votre stack).

Quand un incident survient, le temps perdu à “deviner” l’origine est souvent plus coûteux que le temps passé à rendre la traçabilité claire.

Comment savoir que c’est efficace

Vous cherchez des signaux simples, pas un sentiment.

Après déploiement:

    Les fichiers légitimes continuent de s’afficher (images, PDFs téléchargés). Les requêtes vers des scripts déposés dans uploads ne déclenchent plus d’exécution. Vos logs montrent la présence de blocages (ou des tentatives) au lieu d’exécutions réussies.

Si vous avez un accès à un environnement de test, vous pouvez valider une règle en déposant un fichier témoin qui ne fait rien de dangereux, juste une trace de réponse. Sur un site de production, je recommande plutôt de valider par lecture de logs, ou par un environnement de staging. La prudence est surtout une question de risque opérationnel, pas de peur.

Une note sur la défense en profondeur autour des comptes

Bloquer les fichiers dangereux dans uploads traite une partie du problème, la plus visible côté serveur. Mais si l’attaquant a déjà un accès administrateur, il peut contourner en déposant d’autres charges utiles ailleurs.

La priorité dans la “cause” est généralement:

    corriger un plugin ou un thème vulnérable, changer les mots de passe, forcer une rotation des clés et des sessions, vérifier les rôles et les comptes inconnus.

Le dossier uploads n’est pas l’origine, c’est souvent le théâtre. Et un théâtre qu’on sécurise sans fermer les coulisses reste un théâtre dangereux.

Conclusion opérationnelle: ce qui marche vraiment

Pour renforcer sécurité WordPress, la mesure la plus robuste consiste à empêcher l’exécution de scripts dans wp-content/uploads, que vous soyez sur Apache ou Nginx. Ajoutez ensuite un filtrage raisonnable des uploads côté WordPress, et gardez un œil sur l’état réel des fichiers présents.

Ce qui fait la différence entre une protection “papier” et une protection utile, c’est la combinaison: bonnes règles serveur, déploiement maîtrisé, tests sur staging, et traitement des fichiers déjà présents si incident il y a eu. Dans la pratique, c’est rarement une règle unique qui fait tout, c’est la cohérence de l’ensemble.

Si vous me dites votre configuration (Apache ou Nginx, gestion PHP via mod_php ou PHP-FPM, site simple ou multisite), je peux vous proposer des variantes de règles plus adaptées au contexte, avec les points d’attention propres à votre stack.