
PoC de escalada de privilégios sem autenticação para WordPress Events Manager < 7.4.1; descobre IDs de posts/usuários em colisão e eleva os alvos para administrador via REST ou wp-admin.
| Produto | Events Manager (plugin WordPress) |
| Versões afetadas | 7.1.0 – 7.4.0.x |
| Versão corrigida | 7.4.1 |
| Fraqueza | CWE-269: Improper Privilege Management |
| Severidade | CVSS 3.1: 9.8 (Crítica) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| ID WPVDB | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| Pesquisador original | Jakub Herman |
| Write-up e PoC | ghostpel |
poc.py).As versões 7.1.0 a 7.4.0.x do Events Manager contêm uma vulnerabilidade de escalada de privilégios que permite a um atacante completamente não autenticado assumir o controle de qualquer conta de usuário cujo ID colida com o ID de um dos posts do próprio plugin (event / location / event-recurring / location-recurring). A colisão pode ser forçada em sites com reservas de convidados (guest bookings) habilitadas (o padrão): cada reserva de convidado cria uma conta de usuário real e avança o contador de autoincremento do wp_users em um, permitindo que um atacante "caminhe" pelo espaço de IDs de usuário até atingir um ID de post de event/location.
A causa raiz: o filtro map_meta_cap do plugin descarta a lista de capacidades que o WordPress já havia calculado ($caps = []) para qualquer meta capacidade, sempre que o ID do objeto da capacidade resolve para um post de event/location — incluindo capacidades do núcleo do WordPress, como edit_user, delete_user, promote_user e remove_user. Uma lista de capacidades vazia é interpretada como "nenhuma capacidade necessária" — ou seja, permitido para todos, incluindo usuários desconectados.
Consequências (todas não autenticadas, via API REST do WP): alterar a senha de qualquer conta em colisão, escaloná-la para administrator ou excluí-la.
Versão analisada: 7.4.0.1 (vulnerável) — comparada (diff) com 7.4.1 (corrigida).
| Versão | Status |
|---|---|
| ≤ 7.0.5 | Não afetada (o legado em_map_meta_cap lida apenas com as capacidades próprias do EM) |
| 7.1.0 – 7.4.0.x | Vulnerável (o sistema de arquétipos com o map_meta_cap defeituoso foi introduzido na versão 7.1.0) |
| 7.4.1 | Corrigida |
map_meta_cap esvazia $capsclasses/em-archetypes.php (versão 7.4.0.1):
| Linha | Código | Função |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Filtro global e incondicional — executa para toda verificação de meta-capacidade do WordPress, incluindo as do núcleo e as de usuários desconectados |
:547 | if ( !empty( $args[0] ) ) { | O objeto da capacidade é tratado como um ID de post |
:548 | $post = get_post($args[0]); | Colisão de ID: o ID do usuário alvo (para capacidades como edit_user) é tratado como um ID de post |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | A lista de capacidades é descartada incondicionalmente — mesmo que a capacidade solicitada não seja uma capacidade de arquétipo read/edit/delete_event |
:568/577/584 | if/elseif branches | $caps só é preenchido novamente quando $cap corresponde exatamente a uma meta-capacidade de arquétipo; para edit_user, delete_user, promote_user, remove_user nenhum ramo corresponde → $caps permanece vazio |
:597 | return $caps; | Array vazio = "nenhuma capacidade necessária" = PERMITIDO para qualquer um, incluindo o ID de usuário 0 |
Consequência: toda verificação de current_user_can('edit_user', X) / delete_user / promote_user / remove_user retorna TRUE sempre que X é igual ao ID de um post de event/location. O plugin "descarta as decisões de controle de acesso que o WordPress já havia tomado" — exatamente como descrito pelo WPScan.
Verificado comparando (diff) 7.4.0.1 com 7.4.1: a redefinição agora é protegida por uma correspondência exata de capacidade por ramo (ex.: && $c['read'][$post->post_type] == $cap), portanto $caps só é esvaziado quando a capacidade solicitada é realmente uma meta-capacidade de arquétipo. Comentário do patch: "Redefinir em qualquer capacidade que carregue objeto esvaziava a lista de requisitos para capacidades não relacionadas (ex.: edit_user, promote_user), o que era interpretado como permissão."
Os endpoints REST de Usuários (/wp-json/wp/v2/users/{id}) não possuem barreira global de login — o controle de acesso é feito puramente por callbacks de permissão, todos passando pelo mesmo filtro map_meta_cap:
| Ação | Endpoint / função do núcleo | Capacidade contornada |
|---|---|---|
| Alterar a senha da conta alvo | PUT /wp-json/wp/v2/users/{id} com {"password":"..."} → wp_update_user() (verificação interna de edit_user) | edit_user |
| Escalonar a conta para Administrador | PUT /wp-json/wp/v2/users/{id} com {"roles":["administrator"]} | promote_user |
| Excluir uma conta | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (verificação interna de delete_user) | delete_user |
| (Multisite) remover um usuário do blog | remove_user | remove_user |
Efeito colateral: edit_comment também é afetado quando um ID de comentário coincide com um ID de post de event/location (a tabela wp_comments compartilha o espaço numérico).
A colisão existe porque wp_users.ID e wp_posts.ID são sequências de autoincremento independentes que inevitavelmente se sobrepõem. Reservas de convidados forçam essa colisão:
em-install.php:896 — dbem_bookings_anonymous = 1: reservas de convidados habilitadas por padrão; :871 dbem_bookings_registration_disable = 0 (registro habilitado).em-actions.php:340-344 — booking_add está em $booking_nopriv_actions → chamável sem login (via wp_ajax_nopriv_booking_add, prioridade 999999, linhas :817-830, ou wp_loaded :11).em-actions.php:360-375 — fluxo do booking_add: em_verify_nonce('booking_add') → get_post() → validate() → em_booking_add_registration() → $EM_Bookings->add(). (O nonce é apenas proteção contra CSRF — o nonce booking_add é renderizado no formulário público de reserva, portanto qualquer pessoa pode obtê-lo.)em-functions.php:405-417 — ramo de convidado: em_register_new_user($user_data) com o e-mail do atacante.em-functions.php:510 — wp_insert_user($user_data) → o autoincremento de wp_users avança em 1 a cada reserva de convidado → o atacante controla a taxa de crescimento do contador de IDs de usuário até atingir o ID de post de event/location desejado (WPScan: "cada reserva de convidado cria uma conta de usuário real e avança o contador de IDs de usuário em um").