
PoC d'élévation de privilèges non authentifiée pour WordPress Events Manager < 7.4.1 ; découvre les identifiants post/utilisateur en collision et élève les cibles au rang d'administrateur via REST ou wp-admin.
| Produit | Events Manager (plugin WordPress) |
| Versions affectées | 7.1.0 – 7.4.0.x |
| Version corrigée | 7.4.1 |
| Faiblesse | CWE-269 : Gestion de privilèges inappropriée |
| Sévérité | CVSS 3.1 : 9.8 (Critique) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| ID WPVDB | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| Chercheur d'origine | Jakub Herman |
| Write-up & PoC | ghostpel |
poc.py).Les versions 7.1.0 à 7.4.0.x d'Events Manager contiennent une vulnérabilité d'élévation de privilèges qui permet à un attaquant totalement non authentifié de prendre le contrôle de tout compte utilisateur dont l'ID entre en collision avec l'ID de l'un des posts du plugin (event / location / event-recurring / location-recurring). La collision peut être forcée sur les sites où les réservations invités sont activées (comportement par défaut) : chaque réservation invité crée un véritable compte utilisateur et fait avancer le compteur d'auto-incrémentation de wp_users d'un cran, permettant à un attaquant de « parcourir » l'espace des ID utilisateurs jusqu'à un ID de post event/location.
La cause racine : le filtre map_meta_cap du plugin écarte la liste de capacités que WordPress a déjà calculée ($caps = []) pour toute méta-capacité, dès lors que l'ID de l'objet de la capacité se résout vers un post event/location — y compris les capacités du noyau WordPress telles que edit_user, delete_user, promote_user et remove_user. Une liste de capacités vide est lue comme « aucune capacité requise » — c'est-à-dire autoriser tout le monde, y compris les utilisateurs déconnectés.
Conséquences (toutes non authentifiées, via l'API REST de WP) : modifier le mot de passe de tout compte en collision, l'élever au rôle administrator, ou le supprimer.
Version analysée : 7.4.0.1 (vulnérable) — comparée (diff) à 7.4.1 (corrigée).
| Version | Statut |
|---|---|
| ≤ 7.0.5 | Non affectée (l'ancien em_map_meta_cap ne gère que les capacités propres à EM) |
| 7.1.0 – 7.4.0.x | Vulnérable (le système d'archétypes avec le map_meta_cap défectueux a été introduit en 7.1.0) |
| 7.4.1 | Corrigée |
map_meta_cap vide $capsclasses/em-archetypes.php (version 7.4.0.1) :
| Ligne | Code | Rôle |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Filtre global, inconditionnel — s'exécute pour chaque vérification de méta-capacité WordPress, y compris celles du noyau et celles des utilisateurs déconnectés |
:547 | if ( !empty( $args[0] ) ) { | L'objet de la capacité est pris comme un ID de post |
:548 | $post = get_post($args[0]); | Collision d'ID : l'ID utilisateur cible (pour des capacités comme edit_user) est traité comme un ID de post |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | La liste de capacités est écartée inconditionnellement — même si la capacité demandée n'est pas une capacité d'archétype read/edit/delete_event |
:568/577/584 | if/elseif branches | $caps n'est réapprovisionnée que lorsque $cap correspond exactement à une méta-capacité d'archétype ; pour edit_user, delete_user, promote_user, remove_user aucune branche ne correspond → $caps reste vide |
:597 | return $caps; | Tableau vide = « aucune capacité requise » = AUTORISER tout le monde, y compris l'utilisateur ID 0 |
Conséquence : chaque vérification current_user_can('edit_user', X) / delete_user / promote_user / remove_user renvoie VRAI dès lors que X est égal à l'ID d'un post event/location. Le plugin « écarte les décisions de contrôle d'accès que WordPress avait déjà prises » — exactement comme décrit par WPScan.
Vérifié en comparant 7.4.0.1 à 7.4.1 : la réinitialisation est désormais protégée par une correspondance exacte de capacité par branche (par ex. && $c['read'][$post->post_type] == $cap), donc $caps n'est vidée que lorsque la capacité demandée est réellement une méta-capacité d'archétype. Commentaire du patch : « Réinitialiser sur toute capacité portant un objet vidait la liste d'exigences pour des capacités sans rapport (par ex. edit_user, promote_user), ce qui était interprété comme une autorisation. »
Les endpoints REST Users (/wp-json/wp/v2/users/{id}) n'ont pas de passerelle de connexion globale — le contrôle d'accès repose uniquement sur les callbacks de permission, qui transitent tous par le même filtre map_meta_cap :
| Action | Endpoint / fonction du noyau | Capacité contournée |
|---|---|---|
| Modifier le mot de passe du compte cible | PUT /wp-json/wp/v2/users/{id} avec {"password":"..."} → wp_update_user() (vérification interne edit_user) | edit_user |
| Élever le compte au rôle Administrateur | PUT /wp-json/wp/v2/users/{id} avec {"roles":["administrator"]} | promote_user |
| Supprimer un compte | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (vérification interne delete_user) | delete_user |
| (Multisite) retirer un utilisateur du blog | remove_user | remove_user |
Effet secondaire : edit_comment est également affecté lorsqu'un ID de commentaire se trouve être égal à un ID de post event/location (la table wp_comments partage le même espace numérique).
La collision existe parce que wp_users.ID et wp_posts.ID sont des séquences d'auto-incrémentation indépendantes qui se chevauchent inévitablement. Les réservations invités la forcent :
em-install.php:896 — dbem_bookings_anonymous = 1 : réservations invités activées par défaut ; :871 dbem_bookings_registration_disable = 0 (inscription activée).em-actions.php:340-344 — booking_add est dans $booking_nopriv_actions → appelable sans connexion (via wp_ajax_nopriv_booking_add, priorité 999999, lignes :817-830, ou wp_loaded :11).em-actions.php:360-375 — flux de booking_add : em_verify_nonce('booking_add') → get_post() → validate() → em_booking_add_registration() → $EM_Bookings->add(). (Le nonce n'est qu'une protection CSRF — le nonce booking_add est inclus dans le formulaire de réservation public, donc n'importe qui peut l'obtenir.)em-functions.php:405-417 — branche invité : em_register_new_user($user_data) avec l'e-mail de l'attaquant.em-functions.php:510 — wp_insert_user($user_data) → l'auto-incrémentation de wp_users avance de 1 à chaque réservation invité → l'attaquant contrôle le rythme de croissance du compteur d'ID utilisateurs jusqu'à ce qu'il atteigne l'ID de post event/location souhaité (WPScan : « chaque réservation invité crée un véritable compte utilisateur et fait avancer le compteur d'ID utilisateurs d'un cran »).