Аннотация. Anywhere Elementor 1.3.0 — вспомогательный конструктор страниц для WordPress с заявленной базой более 70 000 установок — несёт в слое AJAX набор дефектов нарушенного контроля доступа. Общая первопричина одна: одноразовый токен AJAX выводится в HTML каждой фронтенд-страницы для каждого посетителя. Токен, стерегущий дверь, которую видят все, не стережёт ничего. Вдобавок обработчики не выполняют никакой авторизации — ни на уровне объекта, ни на уровне прав, — и в итоге получается раскрытие контента без аутентификации и запись опции с низкими правами. В статье разобраны класс уязвимости, шаблон первопричины, концептуальный путь к уязвимому месту, воздействие и конкретные рекомендации по исправлению и обнаружению. По правилам скоординированного раскрытия полный рабочий PoC удержан до выхода исправления; всё изложенное носит защитный и образовательный характер.
Ключевые слова: WordPress, нарушенный контроль доступа, IDOR, неправильное использование nonce, проверки прав, авторизация, безопасное кодирование.
1. Введение
Anywhere Elementor расширяет конструктор страниц Elementor: созданный в нём контент можно выводить по всему сайту через шорткоды и AJAX-обработчики. При заявленных более чем 70 000 активных установок плагин работает на большом числе рабочих сайтов, и многие из них держат непубличный контент — черновики, частные записи, шаблоны, документы отдельных авторов — рядом с публичными страницами.
Ни один из описанных дефектов не тянет на громкую уязвимость уровня удалённого выполнения кода. Каждый — это пробел в авторизации, а пробелы в авторизации относятся к тихому типу сбоев. Они приводят к утечке непубличного контента, раскрывают внутренние схемы полей, позволяют пользователям с низкими правами менять общесайтовые настройки — и при этом плагин продолжает исправно работать для легитимных пользователей. Именно поэтому они проходят код-ревью незамеченными. Шаблон ниже стоит научиться узнавать с первого взгляда и владельцу сайта, и разработчику плагинов.
2. Затронутый компонент
| Плагин | Anywhere Elementor — slug anywhere-elementor |
| Протестированная версия | 1.3.0 (актуальная на момент тестирования) |
| Активных установок | 70 000+ |
| Класс уязвимости | Нарушенный контроль доступа — IDOR-раскрытие контента без аутентификации/с низкими правами и запись опции с низкими правами (CWE-862 Отсутствие авторизации, CWE-639 Обход авторизации через управляемый пользователем ключ) |
| Честная серьёзность | Medium (для наиболее значимых проблем); несколько сопутствующих проблем — Low |
| CVSS | На момент написания не присвоен — выдуманная оценка не приводится |
| Статус | Свежая; подтверждена в исходном коде актуальной версии (достижимость фронтенд-nonce и отсутствие проверок прав проверены в изолированной localhost-лаборатории); без патча |
Общую слабость делят несколько обработчиков:
- A.
ae_post_data— отображает произвольный непубличный контент Elementor по id (без аутентификации). - B.
ae_repeater_data— читает значения полей-повторителей ACF по id (без аутентификации). - C. Шорткод
[INSERT_ELEMENTOR id=..]— Участник/Автор может отобразить любой документ Elementor по id. - D.
save_modules(aep_module) — Подписчик (или CSRF против администратора) записывает опцию активации модулей плагина; нет ни nonce, ни проверки прав. - E.
ae_prev_post/ae_prev_term/ae_acf_repeater_fields/ae_acf_flexible_fields— Подписчик перечисляет схему полей ACF / предпросматривает контент по id (отсутствует проверка прав). - F.
ae_term_data— перечисляет термины любой таксономии, включая непубличные (без аутентификации).
3. Технический анализ
У кластера две разные первопричины.
3.1 Фронтенд-nonce, принятый за барьер аутентификации
Одноразовый токен AJAX плагина (aep_ajax_nonce) помещается в JavaScript-объект (aepro.aep_nonce) на каждой фронтенд-странице, для каждого посетителя; постановка скриптов в очередь идёт на wp_enqueue_scripts вообще без заслона аутентификации. Обработчики nopriv затем проверяют запросы только через wp_verify_nonce('aep_ajax_nonce') — и больше ничем.
Nonce в WordPress — это CSRF-токен, а не механизм аутентификации или авторизации. Когда один и тот же nonce печатается в публичном HTML, доступном кому угодно, его проверка доказывает ровно одно: запрос нёс значение, уже опубликованное для всего мира. Дефект наглядно виден в таком виде:
// Иллюстрация дефектного шаблона (ae_post_data / ae_repeater_data / ae_term_data).
// Зарегистрировано для посетителей без входа:
add_action( 'wp_ajax_nopriv_ae_post_data', 'ajax_post_data' );
function ajax_post_data() {
// ЕДИНСТВЕННЫЙ заслон: nonce, помещённый на каждую публичную страницу.
if ( ! wp_verify_nonce( $_POST['nonce'], 'aep_ajax_nonce' ) ) {
wp_die();
}
// НЕТ проверки статуса / типа / владения / прав для $_POST['post_id'].
$id = (int) $_POST['post_id'];
echo get_builder_content( $id ); // отображает черновики/частное/чужое содержимое
wp_die();
}Ошибка в том, что публичный CSRF-токен принимают за доказательство личности или разрешения. Раз токен публичен, обработчик nopriv фактически работает без аутентификации, а управляемый атакующим post_id (CWE-639) отображается без всякой авторизации на уровне объекта (CWE-862). Ту же форму повторяют читатель значений ACF (B) и перечислитель таксономий (F). Случай с шорткодом (C) — вариация: обработчик принимает id за одной лишь проверкой существования (get_post() !== null), без проверки статуса, типа, пароля, владения или прав, и поэтому Участник, способный предпросмотреть собственный черновик, может отобразить любой документ по id.
3.2 Обработчик, изменяющий состояние, без nonce и без проверки прав
Обработчик save_modules, зарегистрированный как wp_ajax_aep_module, записывает $_POST['moduleData'] в опцию aep_modules через update_option. Он не вызывает ни check_ajax_referer, ни current_user_can:
// Иллюстрация дефектного шаблона (save_modules).
add_action( 'wp_ajax_aep_module', 'save_modules' );
function save_modules() {
// Здесь нет check_ajax_referer(...) и нет current_user_can(...).
// (Собственная строка phpcs:ignore NonceVerification.Missing у плагина подтверждает упущение.)
update_option( 'aep_modules', $_POST['moduleData'] ); // общесайтовые флаги функций
wp_die();
}Соседний обработчик save_config корректно защищён nonce, и на его фоне упущение особенно бросается в глаза: авторизацию знали и применили в том же коде, только не здесь. Записываемые значения приводятся к boolean, поэтому на практике речь о подмене настроек и флагов функций, достижимой через CSRF, а не о хранимом XSS или выполнении кода. И всё же это общесайтовое изменение состояния руками актора без прав.
4. Как достигается дефект (концептуально)
На учебном уровне механизм прост и не требует особого положения в сети.
IDOR без аутентификации (A, B, F). Обработчики опираются на nonce, а этот nonce публикуется в HTML, который плагин отдаёт каждому посетителю на обычных фронтенд-страницах. Значит, «обладание действительным nonce» — не привилегия, а то, что у любого посетителя уже есть, стоит лишь загрузить страницу. Как только запрос проходит проверку nonce, обработчик отображает или возвращает контент, адресованный одним числовым id, и ни разу не спрашивает, разрешено ли вызывающему видеть этот объект. Id, указывающий на черновик, частную запись или чужой документ, обрабатывается так же, как публичный, — и наружу выходит контент, который для публичного доступа не предназначался.
Шорткод (C). Пользователь уровня всего лишь Участника законно создаёт и предпросматривает собственный черновик. Обработчик, вызванный из этого черновика, разрешает любой переданный id и проверяет только, существует ли для него какая-либо запись. Принадлежит ли id документу, который Участнику разрешено видеть, он не проверяет, поэтому предпросмотр черновика отображает указанный документ целиком.
Запись опции (D). Обработчик, хранящий флаги модулей плагина, работает без nonce и без проверки прав. Самый низкий аутентифицированный уровень — Подписчик — достаёт до него напрямую, а раз nonce нет, добраться можно и межсайтовой подделкой запроса (CSRF), нацеленной на вошедшего администратора.
Всё это связывает одна нить: код подтверждает, что запрос корректно сформирован, но ни разу не подтверждает, что вызывающий вправе выполнить это действие над этим объектом. Буквальную последовательность запросов, значения параметров и любые исполняемые команды мы сознательно не публикуем — защитнику они не нужны, чтобы понять риск и закрыть его.
5. Оценка воздействия
В совокупности кластер — это системный нарушенный контроль доступа в слое AJAX плагина.
- Конфиденциальность. Раскрытие без аутентификации или с низкими правами непубличного контента Elementor (A, C), значений повторителей ACF (B), схемы полей ACF и произвольных предпросмотров записей/терминов (E), а также терминов таксономий, включая непубличные (F). На практике это способно привести к утечке неопубликованных маркетинговых страниц, внутренних шаблонов, клиентских данных в полях ACF и самой структуры контента.
- Целостность. Подписчик — или администратор, атакованный через CSRF, — может переключать флаги активации модулей плагина по всему сайту (D). Значения boolean, поэтому это подмена флагов функций и настроек, а не выполнение кода; но актор без прав всё равно меняет поведение плагина для каждого посетителя.
- Доступность. Напрямую не затрагивается.
По отдельности ни одна проблема не критична — отсюда честная серьёзность по каждой: Medium для самых значимых обработчиков (A, C, D) и Low для остальных (B, E, F). Оценку CVSS мы не присваиваем: опубликованной нет, а выдумывать её было бы нечестно. Настоящая суть — в сочетании: несколько независимых пробелов авторизации в одном компоненте, и до каждого можно добраться с минимальными правами или вовсе без них.
6. Исправление
Исправление — перестать считать публичный nonce авторизацией и добавить настоящие проверки на уровне объекта всюду, где управляемый пользователем id выбирает объект:
// Предлагаемое исправление — обработчики отображения / IDOR (A, B, C, E, F):
function ajax_post_data() {
// 1) Настоящий заслон прав, а не только nonce.
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( 'forbidden', 403 );
}
$id = (int) $_POST['post_id'];
// 2) Авторизация на уровне объекта для ЭТОГО объекта.
if ( ! current_user_can( 'edit_post', $id ) ) {
wp_send_json_error( 'forbidden', 403 );
}
// 3) Применить собственную политику показа Elementor к непубличным / библиотечным документам.
echo get_builder_content( $id );
wp_die();
}
// Предлагаемое исправление — обработчик, изменяющий состояние (D):
function save_modules() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'forbidden', 403 );
}
check_ajax_referer( 'aep_ajax_nonce' ); // CSRF-токен В ДОПОЛНЕНИЕ к проверке прав
// ... проверить/внести в белый список ключи, затем update_option( 'aep_modules', ... );
wp_die();
}Конкретно вендору стоит:
- Убрать публичный nonce страницы как единственный заслон в обработчиках отображения/IDOR и добавить проверки по правам (
current_user_can('edit_posts')плюс проверки на уровне объекта —edit_post/статус). - Включить
current_user_can('manage_options')иcheck_ajax_refererвsave_modules. - Применить собственную политику показа Elementor — с ограничением
elementor_libraryи непубличных документов — и в шорткоде, и вae_post_data.
Владельцам сайтов стоит перейти на исправленный выпуск, как только вендор его опубликует. На момент написания исправленной версии нет.
7. Обнаружение и усиление защиты для владельцев сайтов
Пока исправленного выпуска нет, меры ниже прямо вытекают из изложенного механизма, и ни одна не предполагает несуществующей функции продукта:
- Минимум привилегий. Проверьте, у кого роли Подписчик, Участник и Автор. Если открытая регистрация включена, считайте, что недоверенные стороны способны завести учётную запись с низкими правами; отключите самостоятельную регистрацию там, где она не нужна (
Настройки → Общие → Членство). - Отключите то, чем не пользуетесь. Если сайту не нужен вывод непубличного контента плагином через AJAX/шорткод, деактивируйте плагин или эту функцию до выхода исправления.
- Виртуальное патчирование / WAF. Межсетевой экран веб-приложений можно настроить так, чтобы требовать аутентификацию для запросов к
admin-ajax.phpс затронутыми значениямиaction(или ограничивать их частоту и инспектировать), а изменяющий состояниеactionблокировать из сессий без аутентификации или с низкими правами. Это временная мера, а не замена исправлению в коде. - Следите за индикаторами. В логах доступа ищите всплески запросов к
admin-ajax.php, перебирающих много последовательных числовых id, и запросы кadmin-ajax.php, меняющие опции плагина из сессий, откуда вы такого не ждёте. Неожиданное изменение опции модулей/настроек плагина — сигнал сам по себе. - Защитите непубличный контент. Пока плагин активен и без патча, считайте черновики, частные записи и значения полей ACF потенциально доступными и не храните секреты в полях ACF на открытом сайте.
- Держите всё в актуальном состоянии. Следите за журналом изменений плагина и ставьте выпуск безопасности сразу после публикации.
8. Более широкий урок
Принцип в основе всего этого — один из старейших в веб-безопасности и по-прежнему один из самых нарушаемых:
CSRF-токен — это не решение об авторизации. Аутентификация — это не авторизация. «Объект существует» — это не «вызывающий вправе видеть этот объект».
Каждый обработчик здесь провалился на одном из этих трёх пунктов. Nonce доказывает, что запрос был намеренным, и ничего не говорит о том, есть ли у запрашивающего право. Регистрация обработчика для посетителей без входа (nopriv) обязывает делать код безопасным для всего интернета — а значит, ему нужна собственная авторизация, а не токен, который он раздаёт всем. И всякий раз, когда управляемый пользователем id выбирает объект (CWE-639), код обязан проверять разрешение на этот конкретный объект (CWE-862), а не только то, что id во что-то разрешается. Относитесь к каждому обработчику wp_ajax_ / wp_ajax_nopriv_ как к публичному API-эндпоинту и для каждого спрашивайте — кто может это вызвать и над какими объектами? — ещё до того, как запрос дойдёт до данных.
9. Ответственное раскрытие
Это исследование следует скоординированному раскрытию уязвимостей. Находки переданы сопровождающему плагина в частном порядке, а полный рабочий PoC — точные последовательности запросов, значения параметров и команды воспроизведения — сознательно удержан из этого публичного материала до выхода исправления. Сроки любой дальнейшей технической публикации мы согласуем с вендором.
Всё тестирование шло против публичного плагина под лицензией GPL в изолированной, привязанной к localhost лаборатории. Никакие сторонние или рабочие системы не затрагивались, и никакие непубличные данные третьих лиц не собирались.
Источники
- CWE-862: Missing Authorization — https://cwe.mitre.org/data/definitions/862.html
- CWE-639: Authorization Bypass Through User-Controlled Key — https://cwe.mitre.org/data/definitions/639.html
- WordPress Plugin Developer Handbook — Nonces — https://developer.wordpress.org/apis/security/nonces/
- WordPress Plugin Developer Handbook — Checking User Capabilities — https://developer.wordpress.org/plugins/security/checking-user-capabilities/
- WordPress Plugin Developer Handbook — AJAX — https://developer.wordpress.org/plugins/javascript/ajax/
- OWASP — Broken Access Control — https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- FIRST — Калькулятор CVSS v3.1 — https://www.first.org/cvss/calculator/3.1
Опубликовано в защитных и образовательных целях. Noroxi LLC проводит только авторизованные исследования безопасности; операционные детали эксплуатации удержаны до выхода исправления.
Эксплуатируется на этой неделе · по понедельникам
Новые записи KEV, свежие PoC, разбор WordPress и что сделать на неделе. Без аккаунта.