
PoC di escalation dei privilegi senza autenticazione per WordPress Events Manager < 7.4.1; scopre ID di post/utente in collisione ed eleva i target al ruolo di amministratore tramite REST o wp-admin.
| Prodotto | Events Manager (plugin WordPress) |
| Versioni interessate | 7.1.0 – 7.4.0.x |
| Versione corretta | 7.4.1 |
| Debolezza | CWE-269: Gestione impropria dei privilegi |
| Gravità | CVSS 3.1: 9.8 (Critico) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| WPVDB ID | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| Ricercatore originale | Jakub Herman |
| Write-up e PoC | ghostpel |
poc.py).Le versioni di Events Manager dalla 7.1.0 alla 7.4.0.x contengono una vulnerabilità di escalation dei privilegi che consente a un attaccante completamente non autenticato di assumere il controllo di qualsiasi account utente il cui ID collida con l'ID di uno dei post del plugin (event / location / event-recurring / location-recurring). La collisione può essere forzata sui siti con le prenotazioni ospite abilitate (il default): ogni prenotazione ospite crea un account utente reale e fa avanzare di uno il contatore auto-increment di wp_users, consentendo all'attaccante di "camminare" nello spazio degli ID utente fino a raggiungere l'ID di un post event/location.
La causa principale: il filtro map_meta_cap del plugin scarta la lista di capability che WordPress aveva già calcolato ($caps = []) per qualsiasi meta-capability, ogni volta che l'ID dell'oggetto della capability risulta corrispondere a un post event/location — incluse le capability core di WordPress come edit_user, delete_user, promote_user e remove_user. Una lista di capability vuota viene letta come "nessuna capability richiesta" — cioè consentito a tutti, inclusi gli utenti non autenticati.
Conseguenze (tutte non autenticate, tramite la WP REST API): modificare la password di qualsiasi account in collisione, elevarlo a administrator, o eliminarlo.
Versione analizzata: 7.4.0.1 (vulnerabile) — confrontata tramite diff con 7.4.1 (corretta).
| Versione | Stato |
|---|---|
| ≤ 7.0.5 | Non interessata (la em_map_meta_cap legacy gestisce solo le capability proprie di EM) |
| 7.1.0 – 7.4.0.x | Vulnerabile (il sistema di archetipi con la map_meta_cap difettosa è stato introdotto nella 7.1.0) |
| 7.4.1 | Corretta |
map_meta_cap svuota $capsclasses/em-archetypes.php (versione 7.4.0.1):
| Riga | Codice | Ruolo |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Filtro globale e incondizionato — viene eseguito per ogni controllo di meta-capability di WordPress, incluse quelle core e quelle per gli utenti non autenticati |
:547 | if ( !empty( $args[0] ) ) { | L'oggetto della capability viene considerato un post ID |
:548 | $post = get_post($args[0]); | Collisione ID: l'ID utente di destinazione (per cap come edit_user) viene trattato come un ID post |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | La lista di capability viene scartata incondizionatamente — anche se la cap richiesta non è una cap di archetipo read/edit/delete_event |
:568/577/584 | rami if/elseif | $caps viene riempita di nuovo solo quando $cap corrisponde esattamente a una meta-cap di archetipo; per edit_user, delete_user, promote_user, remove_user nessun ramo corrisponde → $caps resta vuota |
:597 | return $caps; | Array vuota = "nessuna capability richiesta" = CONSENTITO a chiunque, incluso l'ID utente 0 |
Conseguenza: ogni controllo current_user_can('edit_user', X) / delete_user / promote_user / remove_user restituisce TRUE ogni volta che X corrisponde all'ID di un post event/location. Il plugin "scarta le decisioni di controllo degli accessi che WordPress aveva già preso" — esattamente come descritto da WPScan.
Verificata confrontando tramite diff la 7.4.0.1 con la 7.4.1: il reset ora è protetto da una corrispondenza esatta della capability per ciascun ramo (es. && $c['read'][$post->post_type] == $cap), quindi $caps viene svuotata solo quando la capability richiesta è realmente una meta-cap di archetipo. Commento della patch: "Il reset su qualsiasi cap con oggetto svuotava la lista dei requisiti per cap non correlate (es. edit_user, promote_user), che viene letta come allow."
Gli endpoint REST Users (/wp-json/wp/v2/users/{id}) non hanno un gate di login globale — il controllo degli accessi avviene esclusivamente tramite callback di permessi, che passano tutti attraverso lo stesso filtro map_meta_cap:
| Azione | Endpoint / funzione core | Capability bypassata |
|---|---|---|
| Modifica la password dell'account di destinazione | PUT /wp-json/wp/v2/users/{id} con {"password":"..."} → wp_update_user() (controllo interno edit_user) | edit_user |
| Eleva l'account ad Amministratore | PUT /wp-json/wp/v2/users/{id} con {"roles":["administrator"]} | promote_user |
| Elimina un account | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (controllo interno delete_user) | delete_user |
| (Multisito) rimuove un utente dal blog | remove_user | remove_user |
Effetto collaterale: anche edit_comment è interessata quando un ID commento capita di essere uguale a un ID di post event/location (la tabella wp_comments condivide lo stesso spazio numerico).
La collisione esiste perché wp_users.ID e wp_posts.ID sono sequenze auto-increment indipendenti che inevitabilmente si sovrappongono. Le prenotazioni ospite la rendono forzabile:
em-install.php:896 — dbem_bookings_anonymous = 1: prenotazioni ospite abilitate di default; :871 dbem_bookings_registration_disable = 0 (registrazione abilitata).em-actions.php:340-344 — booking_add è in $booking_nopriv_actions → richiamabile senza login (tramite wp_ajax_nopriv_booking_add, priorità 999999, righe :817-830, o wp_loaded :11).em-actions.php:360-375 — flusso di booking_add: em_verify_nonce('booking_add') → get_post() → validate() → em_booking_add_registration() → $EM_Bookings->add(). (Il nonce è solo una protezione CSRF — il nonce di booking_add viene renderizzato nel modulo di prenotazione pubblico, quindi chiunque può ottenerlo.)em-functions.php:405-417 — ramo ospite: em_register_new_user($user_data) con l'email dell'attaccante.em-functions.php:510 — wp_insert_user($user_data) → l'auto-increment di wp_users avanza di 1 per ogni prenotazione ospite → l'attaccante controlla il tasso di crescita del contatore degli ID utente finché non atterra sull'ID del post event/location desiderato (WPScan: "ogni prenotazione ospite crea un account utente reale e fa avanzare di uno il contatore degli ID utente").