
PoC de escalada de privilegios no autenticada para WordPress Events Manager < 7.4.1; descubre IDs de publicación/usuario en colisión y escala objetivos a administrador mediante REST o wp-admin.
| Producto | Events Manager (plugin de WordPress) |
| Versiones afectadas | 7.1.0 – 7.4.0.x |
| Versión corregida | 7.4.1 |
| Debilidad | CWE-269: Gestión inadecuada de privilegios |
| Severidad | 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 |
| Investigador original | Jakub Herman |
| Write-up y PoC | ghostpel |
poc.py).Las versiones 7.1.0 a 7.4.0.x de Events Manager contienen una vulnerabilidad de escalada de privilegios que permite a un atacante completamente no autenticado tomar el control de cualquier cuenta de usuario cuyo ID coincida con el ID de una de las entradas del propio plugin (evento / ubicación / evento-recurrente / ubicación-recurrente). La colisión puede forzarse en sitios con reservas de invitados habilitadas (lo predeterminado): cada reserva de invitado crea una cuenta de usuario real y adelanta el contador de autoincremento de wp_users en uno, lo que permite al atacante "recorrer" el espacio de IDs de usuario hasta aterrizar en el ID de una entrada de evento/ubicación.
La causa raíz: el filtro map_meta_cap del plugin descarta la lista de capacidades que WordPress ya había calculado ($caps = []) para cualquier meta-capacidad, siempre que el ID del objeto de la capacidad resulte ser una entrada de evento/ubicación — incluidas capacidades núcleo de WordPress como edit_user, delete_user, promote_user y remove_user. Una lista de capacidades vacía se interpreta como "no se requiere ninguna capacidad", es decir, permitido para todos, incluidos los usuarios sin sesión iniciada.
Consecuencias (todas sin autenticación, vía la API REST de WordPress): cambiar la contraseña de cualquier cuenta que colisione, escalarla a administrator o eliminarla.
Versión analizada: 7.4.0.1 (vulnerable) — comparada con 7.4.1 (corregida).
| Versión | Estado |
|---|---|
| ≤ 7.0.5 | No afectada (el em_map_meta_cap heredado solo gestiona las capacidades propias de EM) |
| 7.1.0 – 7.4.0.x | Vulnerable (el sistema de arquetipos con el map_meta_cap defectuoso se introdujo en 7.1.0) |
| 7.4.1 | Corregida |
map_meta_cap vacía $capsclasses/em-archetypes.php (versión 7.4.0.1):
| Línea | Código | Rol |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Filtro global e incondicional: se ejecuta para todas las comprobaciones de meta-capacidad de WordPress, incluidas las del núcleo y las de usuarios sin sesión iniciada |
:547 | if ( !empty( $args[0] ) ) { | El objeto de la capacidad se toma como un ID de entrada |
:548 | $post = get_post($args[0]); | Colisión de IDs: el ID del usuario objetivo (para capacidades como edit_user) se trata como un ID de entrada |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | La lista de capacidades se descarta incondicionalmente — aunque la capacidad solicitada no sea una capacidad de arquetipo read/edit/delete_event |
:568/577/584 | if/elseif ramas | $caps solo se rellena cuando $cap coincide exactamente con una meta-capacidad de arquetipo; para edit_user, delete_user, promote_user, remove_user ninguna rama coincide → $caps permanece vacía |
:597 | return $caps; | Un array vacío = "no se requiere ninguna capacidad" = PERMITIDO para cualquiera, incluido el ID de usuario 0 |
Consecuencia: toda comprobación de current_user_can('edit_user', X) / delete_user / promote_user / remove_user devuelve TRUE siempre que X sea igual al ID de una entrada de evento/ubicación. El plugin "descarta las decisiones de control de acceso que WordPress ya había tomado", exactamente como lo describe WPScan.
Verificada comparando 7.4.0.1 con 7.4.1: ahora el reinicio está protegido por una coincidencia exacta de capacidad por rama (p. ej. && $c['read'][$post->post_type] == $cap), por lo que $caps solo se vacía cuando la capacidad solicitada es realmente una meta-capacidad de arquetipo. Comentario del parche: "Reiniciar con cualquier capacidad que lleve objeto vaciaba la lista de requisitos para capacidades no relacionadas (p. ej. edit_user, promote_user), lo que se interpretaba como permitido."
Los endpoints REST de usuarios (/wp-json/wp/v2/users/{id}) no tienen una barrera global de inicio de sesión: el control de acceso se basa exclusivamente en callbacks de permisos, todos los cuales pasan por el mismo filtro map_meta_cap:
| Acción | Endpoint / función del núcleo | Capacidad eludida |
|---|---|---|
| Cambiar la contraseña de la cuenta objetivo | PUT /wp-json/wp/v2/users/{id} con {"password":"..."} → wp_update_user() (comprobación interna de edit_user) | edit_user |
| Escalar la cuenta a Administrador | PUT /wp-json/wp/v2/users/{id} con {"roles":["administrator"]} | promote_user |
| Eliminar una cuenta | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (comprobación interna de delete_user) | delete_user |
| (Multisitio) eliminar un usuario del blog | remove_user | remove_user |
Efecto secundario: edit_comment también se ve afectado cuando un ID de comentario coincide con el ID de una entrada de evento/ubicación (la tabla wp_comments comparte el espacio numérico).
La colisión existe porque wp_users.ID y wp_posts.ID son secuencias de autoincremento independientes que inevitablemente se solapan. Las reservas de invitados la fuerzan: