
Неаутентифицированный PoC повышения привилегий для WordPress Events Manager < 7.4.1; обнаруживает конфликтующие ID записей/пользователей и повышает привилегии целевых учётных записей до администратора через REST или wp-admin.
| Продукт | Events Manager (плагин WordPress) |
| Затронутые версии | 7.1.0 – 7.4.0.x |
| Исправленная версия | 7.4.1 |
| Уязвимость | CWE-269: Некорректное управление привилегиями |
| Серьёзность | CVSS 3.1: 9.8 (критическая) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| WPVDB ID | 82767ce2-01e4-46ad-a52b-72d3ab2049bd |
| Первоначальный исследователь | Jakub Herman |
| Разбор и PoC | ghostpel |
poc.py).Версии Events Manager с 7.1.0 по 7.4.0.x содержат уязвимость повышения привилегий, которая позволяет полностью неаутентифицированному атакующему захватить любую учётную запись пользователя, чей ID совпадает с ID одной из собственных записей плагина (event / location / event-recurring / location-recurring). Коллизию можно принудительно вызвать на сайтах с включёнными гостевыми бронированиями (по умолчанию): каждое гостевое бронирование создаёт реальную учётную запись пользователя и увеличивает счётчик автоинкремента wp_users на единицу, что позволяет атакующему «пройтись» по пространству пользовательских ID до ID записи event/location.
Корневая причина: фильтр map_meta_cap плагина отбрасывает уже вычисленный WordPress список возможностей ($caps = []) для любой мета-возможности, когда ID объекта этой возможности оказывается записью event/location — включая базовые возможности ядра WordPress, такие как edit_user, delete_user, promote_user и remove_user. Пустой список возможностей интерпретируется как «возможности не требуются» — то есть разрешено всем, включая неавторизованных пользователей.
Последствия (все без аутентификации, через WP REST API): смена пароля любой совпадающей учётной записи, повышение её до administrator или её удаление.
Проанализированная версия: 7.4.0.1 (уязвимая) — сравнена с 7.4.1 (исправленной).
| Версия | Статус |
|---|---|
| ≤ 7.0.5 | Не затронута (устаревший em_map_meta_cap обрабатывает только собственные возможности EM) |
| 7.1.0 – 7.4.0.x | Уязвима (система архетипов с ошибочным map_meta_cap появилась в 7.1.0) |
| 7.4.1 | Исправлена |
map_meta_cap очищает $capsclasses/em-archetypes.php (версия 7.4.0.1):
| Строка | Код | Роль |
|---|---|---|
:33 | add_filter( 'map_meta_cap', [ static::class, 'map_meta_cap'], 10, 4 ); | Глобальный безусловный фильтр — выполняется при каждой проверке мета-возможностей WordPress, включая возможности ядра и проверки для неавторизованных пользователей |
:547 | if ( !empty( $args[0] ) ) { | Объект возможности принимается как ID записи |
:548 | $post = get_post($args[0]); | Коллизия ID: ID целевого пользователя (для возможностей типа edit_user) обрабатывается как ID записи |
:551 | `if( empty($post->post_type) | |
:563 | `if ( !empty( $c['read'][$post->post_type] ) | |
:564–565 | $caps = []; | Список возможностей отбрасывается безусловно — даже когда запрошенная возможность не является архетипной возможностью read/edit/delete_event |
:568/577/584 | ветки if/elseif | $caps заполняется повторно только когда $cap точно совпадает с архетипной мета-возможностью; для edit_user, delete_user, promote_user, remove_user не совпадает ни одна ветка → $caps остаётся пустым |
:597 | return $caps; | Пустой массив = «возможности не требуются» = РАЗРЕШЕНО для всех, включая пользователя с ID 0 |
Следствие: каждая проверка current_user_can('edit_user', X) / delete_user / promote_user / remove_user возвращает TRUE, когда X равен ID записи event/location. Плагин «отбрасывает решения контроля доступа, уже принятые WordPress» — именно так, как описано WPScan.
Проверено сравнением 7.4.0.1 с 7.4.1: теперь сброс защищён точным совпадением возможности в каждой ветке (например, && $c['read'][$post->post_type] == $cap), поэтому $caps очищается только когда запрошенная возможность действительно является архетипной мета-возможностью. Комментарий в патче: «Сброс при любой возможности с объектом опустошал список требований для несвязанных возможностей (например, edit_user, promote_user), что интерпретируется как разрешение».
У конечных точек REST для пользователей (/wp-json/wp/v2/users/{id}) нет глобального шлюза входа — контроль доступа осуществляется исключительно через колбэки прав, все из которых проходят через тот же фильтр map_meta_cap:
| Действие | Конечная точка / функция ядра | Обойдённая возможность |
|---|---|---|
| Смена пароля целевой учётной записи | PUT /wp-json/wp/v2/users/{id} с {"password":"..."} → wp_update_user() (внутренняя проверка edit_user) | edit_user |
| Повышение учётной записи до администратора | PUT /wp-json/wp/v2/users/{id} с {"roles":["administrator"]} | promote_user |
| Удаление учётной записи | DELETE /wp-json/wp/v2/users/{id}?reassign=... → wp_delete_user() (внутренняя проверка delete_user) | delete_user |
| (Мультисайт) удаление пользователя из блога | remove_user | remove_user |
Побочный эффект: edit_comment также затронута, когда ID комментария случайно совпадает с ID записи event/location (таблица wp_comments использует то же числовое пространство).
Коллизия существует потому, что wp_users.ID и wp_posts.ID — независимые последовательности автоинкремента, которые неизбежно пересекаются. Гостевые бронирования форсируют её:
em-install.php:896 — dbem_bookings_anonymous = 1: гостевые бронирования включены по умолчанию; :871 dbem_bookings_registration_disable = 0 (регистрация включена).em-actions.php:340-344 — booking_add входит в $booking_nopriv_actions → вызывается без входа (через wp_ajax_nopriv_booking_add, приоритет 999999, строки :817-830, или wp_loaded :11).em-actions.php:360-375 — поток booking_add: em_verify_nonce('booking_add') → get_post() → validate() → em_booking_add_registration() → $EM_Bookings->add(). (Nonce — это только защита от CSRF: nonce booking_add выводится на публичной форме бронирования, поэтому его может получить кто угодно.)em-functions.php:405-417 — ветка гостя: em_register_new_user($user_data) с email атакующего.em-functions.php:510 — wp_insert_user($user_data) → автоинкремент wp_users увеличивается на 1 за каждое гостевое бронирование → атакующий контролирует скорость роста счётчика пользовательских ID, пока он не попадёт на нужный ID записи event/location (WPScan: «каждое гостевое бронирование создаёт реальную учётную запись и увеличивает счётчик пользовательских ID на единицу»).