Audit WordPress : sécuriser l’accès au wp-json et limiter les actions

Dès qu’on déploie WordPress à une échelle un peu sérieuse, on finit presque toujours par regarder les endpoints exposés. Et parmi eux, wp-json est souvent celui qui attire le plus l’attention, parce qu’il donne accès à une partie du contenu et surtout à des actions qui peuvent, selon la configuration, accélérer une compromission.

Dans un audit et sécurisation WordPress que j’ai mené sur plusieurs sites de même stack (le même thème, le même plugin de gestion, et des comptes admin historiques), le scénario revenait en boucle : le site ne “casse” pas tout de suite. Il se fait grignoter. Un acteur obtient un point d’entrée, teste les routes REST, repère les failles de logique applicative, puis tente des actions côté serveur. La surface d’attaque commence souvent par wp-json.

Le but ici n’est pas de “fermer WordPress”, ce qui serait contre-productif, mais de réduire ce qui est inutile, protéger ce qui doit rester ouvert, et limiter les actions réalisables via l’API. On va parler de méthode, de décisions concrètes, et des pièges qui font perdre du temps.

Pourquoi wp-json est plus sensible qu’on ne l’imagine

L’API REST de WordPress, via le routeur wp-json, n’est pas un simple “connecteur”. Elle expose des fonctionnalités et des données qui, combinées, peuvent aider à :

    cartographier le site (types de contenu, taxonomies, pages disponibles), identifier des utilisateurs ou des comportements (selon les routes activées), tenter des opérations qui devraient être protégées (mise à jour de contenu, interrogation d’options, actions liées aux formulaires, etc.), pousser le serveur à faire plus de travail qu’il ne faut (requêtes volumineuses, paramètres coûteux, et boucles côté client).

Le piège classique est de confondre “API REST disponible” et “API REST totalement sûre”. La disponibilité ne dit rien du niveau d’authentification requis, du contrôle d’accès réel, ni des erreurs renvoyées par les endpoints. J’ai déjà vu des sites où l’API renvoyait des erreurs “propres”, mais où la logique applicative laissait passer des cas limites. À force de tester, un attaquant finit par trouver une route qui n’est pas à la hauteur de ce qu’on attend.

Et il y a un autre facteur, souvent sous-estimé : l’écosystème plugin. Chaque plugin peut enregistrer ses propres routes REST. Vous pouvez verrouiller le cœur, puis découvrir que la vraie porte n’est pas dans WordPress, mais dans un plugin.

La bonne approche d’audit : commencer par la surface, pas par le patch

Avant de modifier quoi que ce soit, j’aime bien partir d’une cartographie simple. Pas besoin de “tout scanner” comme dans un pentest complet, mais il faut comprendre ce qui est exposé et ce qui répond.

Concrètement, j’évalue au moins trois choses :

Quelles routes REST existent réellement sur votre instance. Entre deux sites “semblables”, le résultat peut changer à cause des plugins et des options. Quel niveau d’accès est attendu pour chaque groupe de routes. Certaines sont publiques, d’autres nécessitent une authentification, d’autres encore doivent être restreintes aux rôles. Comment le serveur réagit aux requêtes erronées ou mal formées. Les messages d’erreur, les codes HTTP, et les latences donnent des indices sur le contrôle d’accès.

On peut ensuite poser des questions de fond, très pratiques : le front a-t-il vraiment besoin de l’API REST pour fonctionner ? Si oui, quelles routes exactes ? Si non, pourquoi l’API reste-t-elle disponible sans nécessité ?

Cette étape évite un écueil que je rencontre souvent : on applique un durcissement “générique” et, deux jours après, un composant du site tombe en panne parce qu’un script dépendait d’une route dont personne n’avait connaissance.

Évaluer ce qui doit rester public, et ce qui doit rester privé

La partie la plus “délicate” de la sécurisation de wp-json, c’est le dosage. Fermer toute l’API peut casser :

    les thèmes et blocs qui chargent du contenu dynamiquement, des formulaires qui passent par un endpoint custom, l’intégration d’un front découplé, des fonctionnalités d’un plugin (recherche, filtre, génération de pages, sync).

Donc, au lieu de viser le “zéro endpoint public”, je vise le “minimum nécessaire”. Dans les projets e-commerce ou média, j’ai souvent vu une approche réaliste : autoriser des routes de lecture pour des contenus spécifiques, mais supprimer ou restreindre les routes d’écriture et les actions sensibles.

La nuance est importante. Même une route “lecture” peut devenir un problème si elle révèle plus que nécessaire (détails internes, structure, métadonnées). Dans d’autres cas, c’est la route d’écriture qui n’est pas bien protégée.

Inspecter les routes : wp-json n’est pas un bloc monolithique

Sur un WordPress standard, on trouve le noyau, puis les ajouts. L’emplacement de départ est généralement le document racine, qui liste les namespaces. Mais ne vous fiez pas uniquement à ce que le “root” affiche.

Pendant mes audits, je fais une vérification croisée, par deux moyens :

    tester l’accès sans authentification à des routes typiques, vérifier ensuite la même chose avec un compte test (abonné, auteur, éditeur), pour voir les différences de comportement.

Cette comparaison met en évidence des incohérences. Par exemple, une route qui renvoie un 200 à tout le monde alors qu’elle devrait renvoyer un 401 ou un 403 quand l’utilisateur n’est pas autorisé. Ou encore une route qui renvoie une réponse, mais laisse passer une action ensuite.

Autre observation utile : certaines routes renvoient un code HTTP “adéquat”, mais une charge utile trop bavarde. Le durcissement ne doit pas se limiter aux codes, il doit toucher au contenu, au contrôle d’accès et aux paramètres.

Durcir l’accès réseau, sans confondre “API fermée” et “API protégée”

Un point qui revient : sécuriser wp-json ne veut pas dire “interdire les requêtes”. Souvent, vous devez gérer deux niveaux :

image

    accès réseau : qui a le droit de contacter wp-json, accès applicatif : qu’est-ce que le serveur accepte comme action et pour quel utilisateur.

On peut agir au niveau réseau avec un pare-feu applicatif, des règles WAF, du filtrage par IP, ou une limitation de débit. Mais si vous bloquez trop, vous risquez de couper des clients légitimes ou des outils de monitoring internes.

Dans un contexte multi-pays, les IP “utiles” peuvent changer. J’ai déjà vu des sites pénalisés parce que la règle WAF était trop strictement basée sur une plage IP obsolète. Une autre difficulté vient des services tiers, par exemple un outil de cache ou un front CDN qui n’a pas les mêmes adresses source selon les régions.

Le bon compromis, quand c’est possible, est de protéger les actions sensibles (auth requise, CSRF si pertinent côté endpoint, vérification de permissions) plutôt que de “tout rendre illisible”.

Protéger les actions : permissions WordPress et capacité réelle

Dans WordPress, la sécurité REST repose fortement sur le système de permissions. Les développeurs inscrivent des callbacks qui vérifient les capacités, souvent via current_user_can(). Si la route est mal écrite, ou si un plugin a choisi un raccourci, l’API devient une voie d’attaque.

C’est ici que je recommande de combiner la technique et l’intention :

    si un endpoint fait une action d’écriture (création, modification, suppression), il doit exiger un utilisateur authentifié et des capacités cohérentes, si un endpoint déclenche une action coûteuse (import, traitement de fichiers, génération), il doit être encore plus strict, et pour tout endpoint “semi-public”, il faut vérifier les paramètres, notamment ceux qui choisissent quel objet est ciblé.

Un cas typique : un endpoint custom qui accepte un identifiant de post en paramètre, puis agit. Si la vérification de capacité n’assure pas que l’utilisateur a le droit sur ce post précis (et pas “sur quelque chose”), un attaquant peut viser des objets qu’il ne contrôle pas.

Ce n’est pas une question “WordPress ou pas”. C’est une question de logique applicative.

Réduire la surface : namespaces et endpoints inutiles

Une pratique efficace consiste à désactiver les routes inutiles. Mais il faut être rigoureux : “inutiles” dépend de votre usage réel.

Si vous gérez un site vitrine, et que le front n’a pas besoin de REST, vous pouvez envisager une fermeture plus large, ou au moins une restriction des namespaces non utilisés.

Si le front a besoin de données, vous pouvez plutôt limiter ce qui répond. Par exemple :

    conserver les routes de lecture nécessaires, supprimer ou bloquer les routes d’écriture, désactiver des namespaces de plugins si vous n’en avez pas besoin.

Ici, la décision n’est pas que technique, elle est aussi organisationnelle : si un plugin dépend de REST pour fonctionner, la coupure devient une dette de maintenance.

Mettre des garde-fous côté serveur : limiter ce que l’API fait vraiment

Au-delà de l’autorisation, il y a la question de la charge. Un endpoint peut être accessible à l’authentification, mais rester exploitable pour faire dégrader le serveur.

Pendant les audits, je regarde des signaux concrets : temps de réponse anormaux, payloads autorisés, endpoints qui acceptent des paramètres trop libres, et absence de limites.

Cela peut mener à une combinaison de mesures :

    limitation de débit (rate limit) sur wp-json, limitation du size de corps de requête si des endpoints acceptent des fichiers ou des payloads volumineux, durcissement des logs, pour détecter les patterns d’essai, validation stricte des paramètres dans le code custom des plugins.

Si vous avez un cache ou un CDN, la mise en cache des endpoints de lecture peut réduire la pression, mais attention aux endpoints qui dépendent de l’utilisateur. Un cache mal réglé peut fuiter des données entre utilisateurs.

Une checklist de durcissement réaliste (sans casser le site)

Voici une approche que j’ai appliquée plusieurs fois pour sécuriser l’accès à wp-json et limiter les actions, tout en gardant un site stable.

Identifier les namespaces et routes réellement utilisés par le front et les plugins (y compris les routes custom). Vérifier les permissions sur les routes d’écriture et les endpoints qui ciblent des objets (post ID, user ID, option keys). Mettre en place une stratégie de restriction réseau pour les endpoints sensibles (WAF, règles IP, ou challenge si besoin). Ajouter une limitation de débit sur wp-json et sur les endpoints d’action, avec des seuils progressifs. Tester avec différents rôles (non connecté, abonné, éditeur, admin) et valider les codes HTTP et le comportement réel.

Cette checklist évite le piège du “tout fermer” et pousse vers une logique d’accès et de charge.

Cas particulier : fermeture de wp-json et conséquences inattendues

Certaines équipes choisissent de bloquer wp-json au niveau serveur, parfois même avec une règle simple dans le firewall. Ça peut marcher, mais la probabilité de casser des fonctions augmente vite.

J’ai vu des sites où :

image

    le moteur de recherche interne dépendait d’une route REST, un constructeur de pages appelait l’API pour récupérer des données, un plugin de sécurité ou de marketing utilisait REST à des fins de vérification.

Quand vous bloquez wp-json, vous devez documenter qui dépend de quoi. Sinon, la “sécurité gagnée” se paie en interruptions. Et comme wp-json est un chemin connu, les outils automatisés vont quand même tenter d’y accéder, ce qui peut déclencher des alertes internes.

Si vous optez pour un blocage partiel (par exemple, uniquement pour certaines méthodes, ou uniquement depuis des pays non prévus), vous gagnez en précision. La méthode joue un rôle, un endpoint d’écriture peut être bloqué, tout en gardant la lecture.

Limiter les actions : les points où ça tourne mal

Les problèmes les plus fréquents que je rencontre sur wp-json, ce ne sont pas forcément des “fautes de plugin” spectaculaires. Souvent, c’est un mélange de détails :

    permission check trop large (vérifie “peut éditer”, mais pas “peut éditer ce contenu”), endpoints qui acceptent des paramètres sans validation (catégorie, auteur, slug, metas), erreurs renvoyées qui aident l’attaquant à affiner, absence de protection contre la répétition (un bot peut spammer un endpoint d’action si la permission existe), trop de routes “public” par défaut.

Ce que je recommande en pratique, c’est de classer vos endpoints par sens : lecture publique, lecture restreinte, écriture, actions.

Ensuite vous appliquez une règle d’accès par catégorie, plutôt que de traiter chaque route au hasard.

Où placer la protection : WordPress, plugin, ou reverse proxy ?

On peut agir à plusieurs étages. Le bon choix dépend de votre architecture :

    côté WordPress (permissions, validation, suppression de routes) : c’est propre, mais demande du travail au niveau application. Si des plugins inscrivent des routes, vous devrez parfois les ajuster via hooks ou filtres. côté plugin : c’est souvent le meilleur endroit si vous contrôlez le code. Mais si le plugin est un tiers, vous êtes limité aux options disponibles. côté reverse proxy/WAF : c’est rapide à mettre en place, utile pour la rate limit et la réduction de scans. Mais ce n’est pas une validation métier.

Dans mon expérience, le meilleur résultat vient d’un empilement raisonné. Le WAF ou le reverse proxy bloque le bruit et limite les tentatives, et WordPress garantit que les autorisations et les validations sont cohérentes quand une requête passe.

Mesurer et vérifier : ne pas “supposer” que c’est sécurisé

Après modifications, je fais toujours une série de tests simples. Pas besoin de batailler avec des scénarios compliqués, il faut vérifier des invariants :

    Non connecté, les endpoints d’écriture doivent refuser. Les endpoints restreints doivent exiger les bons rôles. Les paramètres sensibles doivent être validés et bornés. Les réponses doivent être cohérentes, pas de divulgation inutile. Les tentatives répétées doivent être freinées.

La vérification se fait aussi sur les logs. Sur un audit et sécurisation WordPress, je privilégie les signaux qui indiquent que quelqu’un tente des routes malgré une permission refusée. Si vous voyez seulement quelques erreurs et ensuite plus rien, c’est bon signe. Si les erreurs continuent en boucle, c’est que votre protection applicative ne suffit pas ou que le bruit est juste plus important que prévu.

Mini guide de mise en pratique (sans entrer dans du code fragile)

Selon votre niveau d’intervention, vous pouvez agir de plusieurs manières. Si vous travaillez avec des développeurs, ils peuvent adapter le code des plugins, ou appliquer des hooks pour filtrer les permissions des routes existantes.

Si vous êtes en mode “administration”, vous pouvez déjà faire beaucoup via :

    la configuration WordPress (désactivation de fonctionnalités inutiles), la limitation d’accès réseau, les plugins de sécurité qui gèrent l’API (avec prudence), les règles WAF et la journalisation.

L’erreur courante, c’est de choisir un plugin de sécurité “qui fait tout” sans comprendre ce qu’il modifie. Certains durcissements peuvent masquer des erreurs, ce qui complique l’audit. D’autres peuvent bloquer des outils internes.

Je conseille de garder un carnet de changements : quelle règle a été modifiée, à quel moment, et sur quels endpoints on s’attend à voir un effet. Quand quelque chose casse, vous saurez où chercher.

Cas concret : le scénario du “compromis acceptable” sur un site multi-plugin

Sur un site d’actualités, l’équipe voulait limiter l’exposition de wp-json sans casser :

    une recherche front, une passerelle pour les alertes email, un système de synchronisation interne alimenté par un script.

Le point de départ a été l’identification des namespaces. Une partie de l’API était effectivement utilisée pour la recherche, mais les endpoints d’actions n’étaient pas nécessaires depuis le front public.

Ils ont donc mis en place une restriction ciblée :

    le front gardait uniquement les appels dont il avait besoin, les endpoints d’action exigeaient l’authentification et vérifiaient les capacités sur l’objet, et une limite de débit a été ajoutée pour wp-json sur les routes susceptibles d’être appelées en boucle par des bots.

Le résultat a été tangible, non seulement en sécurité, mais aussi en performance. Les erreurs répétées ont diminué, et le serveur passait moins de temps à traiter des requêtes inutiles.

Ce genre de projet montre la logique : vous ne cherchez pas un bouton “sécuriser l’API”, vous cherchez une réduction progressive et mesurable du risque.

Les limites de certaines protections “par défaut”

Un durcissement d’API a toujours des compromis. Par exemple :

    réduire trop les routes publiques peut casser le front, ou augmenter le trafic si le front retombe sur d’autres mécanismes, imposer une authentification universelle sur l’API est rarement viable, sauf site très contrôlé, masquer les endpoints peut diminuer les scanners, mais n’empêche pas un attaquant déterminé de trouver des routes via les namespaces activés ou par analyse du code.

La sécurité, c’est la combinaison de la réduction de surface, du contrôle d’accès applicatif, et de la limitation de l’abus. Une seule couche n’est pas suffisante.

Quelles priorités donner si vous manquez de temps

Si vous devez arbitrer, commencez par ce qui a le meilleur ratio impact effort. Sans transformer ça en liste exhaustive, je pense aux priorités suivantes en termes de bénéfice :

    routes d’écriture et endpoints d’action, vérification stricte des permissions côté objet, rate limit sur wp-json, réduction des namespaces de plugins inutiles, cohérence des réponses (éviter les fuites et les détails exploitables).

C’est souvent là que se trouvent les gains. Le reste, comme la fermeture totale de certains endpoints de lecture, devient intéressant ensuite, quand l’infrastructure est https://gardewp.fr/securite-wordpress/ stable.

Retours d’expérience : ce qui aide vraiment pendant l’audit

Quand je fais un audit, les éléments qui accélèrent les décisions sont rarement techniques uniquement. Ce sont aussi des informations opérationnelles :

    quels scripts appellent wp-json et à quelle fréquence, quels rôles sont utilisés par les équipes (et si les rôles sont cohérents avec l’organisation), quels plugins sont actifs et lesquels ajoutent des routes custom, quelles contraintes existent côté performance et disponibilité.

Sans ces données, on fait des suppositions. Et sur wp-json, les suppositions coûtent vite, parce que l’API est un point central de communication.

Si vous voulez un résultat fiable, prenez le temps de relier l’intention métier à la configuration technique. Un endpoint est une promesse, et une promesse qui n’est pas tenue, ou qui l’est trop largement, devient un risque.

Conclusion implicite, mais actionnable

Sécuriser l’accès au wp-json et limiter les actions, ce n’est pas une opération “unique”. C’est un cycle : cartographier, restreindre, mesurer, ajuster. Le bon objectif n’est pas de cacher WordPress, mais de rendre l’API moins utile à un attaquant et plus fiable pour les usages légitimes.

Si vous adoptez une démarche orientée endpoints, avec une vérification par rôles et une protection contre l’abus, vous réduisez nettement la surface d’attaque. Et surtout, vous gardez le site fonctionnel, ce qui est la vraie condition d’une sécurisation durable.