Analyse défensive, décomposition du correctif et scanner de détection pour CVE-2026-14378 (WordPress DevKit Pro Plugin <= 2.3.0).
CVE-2026-14378 est un contournement d'authentification non authentifié critique (CVSS 9.8) dans le plugin DevKit Pro pour WordPress, affectant toutes les versions jusqu'à 2.3.0 inclus.
Le plugin inclut un mécanisme de changement d'utilisateur pour les développeurs. Lorsqu'un administrateur « bascule » vers un autre compte utilisateur, il stocke l'ID de l'administrateur dans un cookie nommé original_user_id. La faille : le plugin affiche un formulaire HTML « switch back » sur n'importe quelle page dès que ce cookie est présent — y compris pour les visiteurs non authentifiés qui définissent le cookie manuellement. Pire encore, l'étape de vérification du nonce contrôle la capacité d'administration de l'utilisateur du cookie, et non la session de l'appelant — le serveur remet donc volontiers un cookie de session administrateur authentifié à quiconque envoie le bon POST.
Résultat net : aucune credential requise pour une prise de contrôle complète de l'administration WordPress.
| Attribut | Détails |
|---|---|
| ID CVE | CVE-2026-14378 |
| Classe de vulnérabilité | Authentification incorrecte (CWE-287) |
| Score CVSS v3.1 | 9.8 (Critique) |
| Vecteur CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Logiciel affecté | Plugin WordPress DevKit Pro (dplugins) |
| Versions vulnérables | <= 2.3.0 |
| Version corrigée | 2.3.1 |
| Date de divulgation | 02 oct. 2026 |
DevKit Pro inclut un utilitaire de développement qui permet aux administrateurs du site de « basculer » vers d'autres comptes utilisateurs pour tester les permissions. Lorsque l'administrateur utilise le switch :
Set-Cookie: original_user_id=1isset($_COOKIE['original_user_id'])wp_footer() avec un formulaire POST caché contenant un nonce fraisadmin-post.php?action=revert_switchwp_set_auth_cookie($user_id) pour restaurer la session d'origineLe gestionnaire du hook wp_footer :
// DevKit Pro <= 2.3.0 — render_switch_back_bar()
public function render_switch_back_bar() {
// FLAW: Only checks if cookie exists — no session validation!
if ( isset( $_COOKIE['original_user_id'] ) ) {
$user_id = (int) $_COOKIE['original_user_id'];
$nonce = wp_create_nonce( 'devkit_revert_switch_' . $user_id );
echo '<div id="devkit-pro-switch-back" class="devkit-switch-bar" style="display:none;">';
echo ' <form id="devkit-revert-form" action="' . admin_url('admin-post.php') . '" method="POST">';
echo ' <input type="hidden" name="action" value="revert_switch" />';
echo ' <input type="hidden" name="_wpnonce" value="' . $nonce . '" />';
echo ' <input type="hidden" name="target_user_id" value="' . $user_id . '" />';
echo ' </form>';
echo '</div>';
echo '<!-- DevKit Pro 2.3.0 Switch Component Active -->';
}
}
Le gestionnaire POST qui traite la soumission du formulaire :
// DevKit Pro <= 2.3.0 — handle_revert_switch()
public function handle_revert_switch() {
$user_id = (int) $_POST['target_user_id'];
$nonce = sanitize_text_field( $_POST['_wpnonce'] );
if ( ! $this->verify_nonce_and_capability( $user_id, $nonce ) ) {
wp_die( 'Unauthorized' );
}
wp_set_current_user( $user_id );
wp_set_auth_cookie( $user_id ); // <-- Grants authenticated session to caller
wp_redirect( admin_url() );
exit;
}
private function verify_nonce_and_capability( $user_id, $nonce ) {
if ( ! wp_verify_nonce( $nonce, 'devkit_revert_switch_' . $user_id ) ) {
return false;
}
// CRITICAL FLAW: Checks the cookie user's capability, not the caller's!
return user_can( $user_id, 'manage_options' );
}
user_can( $user_id, 'manage_options' ) répond à la question : « L'utilisateur #1 possède-t-il la capacité manage_options ? »
La réponse pour l'utilisateur #1 (le premier administrateur WordPress créé) est toujours true.
Il faudrait plutôt demander : « La personne effectuant cette requête HTTP possède-t-elle la capacité manage_options ? »
La vérification correcte est current_user_can('manage_options'), qui renverrait false pour un visiteur non authentifié.
L'intégralité de cette procédure a été exécutée et vérifiée sur une instance WordPress active fonctionnant dans un conteneur Podman local (http://localhost:8080) avec DevKit Pro 2.3.0 actif.
Pour reproduire cela localement, vous avez besoin de :
Configuration rapide d'un lab Podman :
# Start MariaDB
podman run -d --name wp-db \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wpuser \
-e MYSQL_PASSWORD=wppass \
mariadb:10.6
# Start WordPress
podman run -d --name wp-app \
-p 8080:80 \
--link wp-db:mysql \
-e WORDPRESS_DB_HOST=mysql \
-e WORDPRESS_DB_NAME=wordpress \
-e WORDPRESS_DB_USER=wpuser \
-e WORDPRESS_DB_PASSWORD=wppass \
wordpress:latest