
Un plugin WordPress exposant un serveur MCP via l'API REST, avec le modèle de sécurité comme point central -- comble la faille de contournement OAuth de type CVE-2026-15015 en n'ayant jamais cette surface du tout.
En termes simples : cela permet à un agent IA de lire et d'écrire légèrement sur un site WordPress via un véritable mot de passe d'application WordPress — le même type d'identifiant que wp-admin délivre déjà — et rien d'autre. Il n'y a pas de formulaire d'inscription, pas d'enregistrement de client OAuth, pas de point de terminaison de jeton d'aucune sorte ici. Un plugin dans ce même domaine a livré exactement cela, et c'est ainsi qu'un attaquant non authentifié est reparti avec un accès administrateur complet.
Un plugin WordPress exposant un serveur MCP via l'API REST, avec le modèle de sécurité comme point central, et non comme argument de vente.
CVE-2026-15015, CVSS 9.8 : le MountDev AI MCP Connector pour WordPress exposait un point de terminaison d'enregistrement dynamique de client OAuth 2.1 accessible publiquement, aux côtés d'un point de terminaison d'autorisation qui ne vérifiait pas non plus qui faisait la demande. Un attaquant enregistre son propre client OAuth — aucun identifiant requis, le point de terminaison est non authentifié par conception, c'est ce que signifie « enregistrement dynamique de client » — le fait passer par le point de terminaison d'autorisation sans surveillance, et reçoit un jeton bearer lié à un compte administrateur. Contrôle total du site : contenu, utilisateurs, réglages. Rien n'était mal configuré ; le flux fonctionnait exactement comme l'enregistrement de client OAuth est censé fonctionner. Il n'aurait simplement jamais dû être accessible sans qu'un administrateur soit déjà présent pour l'approuver.
Le correctif qui se généralise n'est pas « implémenter OAuth plus
soigneusement ». C'est de ne pas avoir cette surface. Ce plugin
n'enregistre aucun point de terminaison d'enregistrement de client, aucun
point de terminaison d'autorisation, et aucun point de terminaison
d'émission de jeton d'aucune sorte. Il n'y a rien contre quoi un attaquant
puisse s'enregistrer, car il n'y a ici rien qui distribue des identifiants.
L'authentification est un mot de passe d'application WordPress qu'un
administrateur a déjà créé pour un utilisateur spécifique depuis wp-admin —
un identifiant qui n'existe que parce qu'un humain disposant de
manage_options a choisi de le créer, à l'avance, hors bande de ce plugin
entièrement.
Aucune des deux n'est nouvelle à elle seule ; exécuter les deux, à dessein, indépendamment l'une de l'autre, c'est ce qui ferme une erreur dans l'une ou l'autre :
1. Mot de passe d'application uniquement — jamais une session par
cookie. Le is_user_logged_in() de WordPress est également vrai pour un
onglet de navigateur détenant un cookie et un nonce valides.
Auth::permission_callback() refuse cette voie
délibérément : il écoute l'action
application_password_did_authenticate que le cœur de WordPress déclenche
spécifiquement lorsque l'authentification par mot de passe d'application en
Basic-auth réussit, et exige ce drapeau, pas seulement « un utilisateur
quelconque est connecté ». Un client MCP n'est pas un onglet de navigateur
avec un nonce ; accepter l'authentification par cookie ici ouvrirait une
voie de type CSRF vers une surface d'outils à laquelle on peut demander, en
anglais, de modifier du contenu, sans aucun bénéfice par rapport à la seule
porte dont ce plugin a réellement besoin.
2. Capacité par outil, plus un verrou d'écriture que la capacité seule ne
peut pas ouvrir. ToolRegistry::call() vérifie
user_can($user_id, $capability) à chaque appel — edit_posts pour les
articles, manage_woocommerce pour les commandes. Séparément, le seul outil
d'écriture, create_draft_post, exige en plus qu'un administrateur ait
activé l'option depuis Réglages → WP Secure MCP, désactivée par défaut.
Un mot de passe d'application qui porte edit_posts ne peut toujours rien
écrire tant qu'un humain n'a pas basculé ce commutateur — une vérification
de capacité et un verrou à l'échelle du site sont deux serrures différentes,
et les tests prouvent qu'aucune ne remplace l'autre
dans un sens comme dans l'autre.
limit, 1–20) bornent ce qu'un seul appel peut renvoyer ; un appel
WP_Query ou wc_get_orders légitimement coûteux coûte toujours ce qu'il
coûte.| Outil | Capacité | Lecture seule | Notes |
|---|---|---|---|
search_posts | edit_posts | Oui | Recherche par mot-clé. Codé en dur sur post_status=publish — ne renvoie jamais de brouillons ni d'articles privés, même à un appelant qui pourrait les voir dans wp-admin. |
get_post | edit_posts | Oui | Récupération par ID. Refuse un véritable article à un véritable ID si son statut n'est pas publish — un ID n'est pas un contournement de la même règle que search_posts applique. |
list_recent_orders | manage_woocommerce | Oui | Statut, total, devise, date uniquement. Jamais le nom, l'e-mail ou l'adresse du client — la visibilité des commandes n'exige pas de remettre à un agent les données personnelles d'un client, vérification de capacité ou non. |
create_draft_post | edit_posts | Non | post_status est un littéral 'draft' dans la source, pas un argument. Cet outil ne peut pas publier sous aucune entrée, même avec les écritures activées. Désactivé à l'échelle du site tant qu'un administrateur n'a pas activé l'option. |
L'entrée tools/list de chaque outil porte les annotations readOnlyHint /
destructiveHint, afin qu'un client puisse décider s'il doit demander à un
humain avant d'en appeler un, sans avoir besoin de savoir déjà ce qu'il fait.
composer install --no-dev
Copiez (ou créez un lien symbolique vers) le répertoire du plugin dans
wp-content/plugins/, activez-le depuis wp-admin, puis créez un mot de
passe d'application pour le compte sous lequel l'agent doit agir :
Utilisateurs → votre profil → Mots de passe d'application.
Les écritures sont désactivées par défaut. Pour autoriser
create_draft_post : Réglages → WP Secure MCP → Outils d'écriture. La
vérification de capacité par outil s'applique toujours par-dessus — basculer
le commutateur à l'échelle du site ne confère à personne une capacité qu'il
n'avait pas déjà.