Перейти к содержимому
Noroxi
Исследования9 мин

Premium Addons for Elementor: нарушение контроля доступа (CWE-862)

Два дефекта отсутствующей авторизации в Premium Addons for Elementor 4.11.110: участник публикует записи, аноним читает закрытые шаблоны библиотеки.

Команда NoroxiТестирование на проникновение и консалтинг

Аннотация. Большинство уязвимостей в плагинах WordPress — это не повреждение памяти, а обработчики, которые забыли спросить, вправе ли вызывающий сделать то, что он запросил. В Premium Addons for Elementor 4.11.110 таких два. Первый даёт участнику (Contributor) создавать опубликованные записи; второй даёт анонимному посетителю читать шаблоны библиотеки Elementor, которые платформа держит закрытыми. Ниже — код первопричины, концептуальный разбор того, почему достижима каждая проверка, и меры защиты. В рамках скоординированного раскрытия рабочий Proof of Concept в статью не входит, пока нет исправления.

Ключевые слова: нарушение контроля доступа, отсутствующая авторизация, CWE-862, проверка возможностей, nonce против возможности, Elementor, безопасность AJAX в WordPress.

1. Введение

Premium Addons for Elementor — распространённый набор виджетов для конструктора страниц Elementor, он работает более чем на 600 000 сайтах. Такой охват и делает его удачным примером. Ошибки здесь не экзотические: это самый частый класс уязвимостей плагинов WordPress — отсутствующая авторизация (CWE-862). Принцип, который они показывают, применим почти к любому плагину: nonce подтверждает намерение, а не право. Обработчик, который проверяет nonce и ни разу не вызывает current_user_can(), — ошибка частая и с высоким эффектом, и Premium Addons допускает её дважды.

Обе проблемы мы подтвердили в исходном коде плагина и воспроизвели в изолированной локальной (localhost) лаборатории на Elementor 4.3.3 и Premium Addons 4.11.110. Уровень честно средний: воздействие ограничено, но в обоих случаях это явное пересечение границы привилегий, которое стоит понимать и от которого стоит защищаться.

2. Затронутый компонент

ПлагинPremium Addons for Elementor — слаг premium-addons-for-elementor
Тестируемая версия4.11.110 (текущая/последняя на момент тестирования)
Активные установки600 000+
СтатусСвежая; подтверждена в коде и воспроизведена в локальной лаборатории

A) handle_live_editor — отсутствующая авторизация → участник создаёт ОПУБЛИКОВАННЫЕ записи

ТипОтсутствующая авторизация / нарушение контроля доступа
CWECWE-862 Missing Authorization
ПривилегияУчастник (любой пользователь с edit_posts; обычно не может публиковать)
CVSS 3.1~5.4 (Средняя) AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L

B) get_elementor_template_content — неаутентифицированное раскрытие шаблона Elementor

ТипНарушение контроля доступа / раскрытие информации
CWECWE-862 / CWE-200
ПривилегияНеаутентифицированный
CVSS 3.1~5.3 (Средняя) AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

3. Технический анализ

3.1 Находка A — проверка nonce вместо проверки возможности

В includes/extras/live-editor.php (v4.11.110) два AJAX-действия регистрируются для каждого авторизованного пользователя:

php
add_action( 'wp_ajax_update_template_title', array( $this, 'update_template_title' ) );   // line 40add_action( 'wp_ajax_handle_live_editor',   array( $this, 'handle_live_editor' ) );       // line 41

Соседний обработчик update_template_title() авторизацию делает верно — проверяет и nonce, и возможность:

php
public function update_template_title() {    check_ajax_referer( 'pa-live-editor', 'security' );    ...    if ( ! current_user_can( 'manage_options' ) ) {          // <-- capability check present        wp_send_json_error( 'Insufficient user permission' );    }    wp_update_post( ... );}

А handle_live_editor() проверку возможности убирает и оставляет только nonce:

php
public function handle_live_editor() {    check_ajax_referer( 'pa-live-editor', 'security' );       // nonce ONLY — no current_user_can()    if ( ! isset( $_POST['key'] ) ) { wp_send_json_error(); }    $key       = sanitize_text_field( wp_unslash( $_POST['key'] ) );    $temp_type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : false;    wp_send_json_success( self::get_or_create_dynamic_template( $key, $temp_type ) );   // -> wp_insert_post(publish)}

В конце цепочки создаётся опубликованная запись:

php
$post_id = wp_insert_post( array(    'post_content' => '',    'post_type'    => 'elementor_library',    'post_title'   => $post_title,              // 'PA Template | #' . substr(md5($key),0,4)    'post_name'    => $post_name,               // 'pa-dynamic-temp-' . $key   (attacker-controlled slug)    'post_status'  => 'publish',                // <-- PUBLISHED    'meta_input'   => self::get_dynamic_template_meta( $type ),) );

Nonce pa-live-editor — это не право, а всего лишь защита от CSRF (межсайтовой подделки запроса). Он подтверждает, что авторизованный пользователь отправил этот запрос сознательно, и только. Авторизацию WordPress держит в отдельном слое — возможностях (capabilities), — потому что «запрос подлинный» и «пользователю это разрешено» суть разные вопросы. Любой участник вправе открыть редактор Elementor на своём черновике, а именно там и выдаётся nonce pa-live-editor, так что пройти проверку nonce ему ничего не стоит. Дальше handle_live_editor() выполняет публикацию, которую участник не должен уметь выполнять. Выдаёт всё несогласованность: update_template_title() и check_temp_validity() проверяют manage_options, а handle_live_editor() и pa_get_editor_template() — нет.

3.2 Находка B — неаутентифицированный обработчик, защищённый только статусом записи

В includes/helpers/ajax-helper.php (v4.11.110) обработчик содержимого шаблона регистрируется ещё и для неаутентифицированных пользователей:

php
add_action( 'wp_ajax_get_elementor_template_content',        array( $this, 'get_template_content' ) );  // line 37add_action( 'wp_ajax_nopriv_get_elementor_template_content', array( $this, 'get_template_content' ) );  // line 38  (UNAUTH)

Ни nonce, ни проверки возможности в обработчике нет. Единственный барьер — проверка статуса записи, и опубликованные записи проходят сквозь него:

php
public function get_template_content() {    $template = isset($_GET['templateID']) ? sanitize_text_field(wp_unslash($_GET['templateID'])) : '';    $is_ID    = isset($_GET['is_id']) ? filter_var($_GET['is_id'], FILTER_VALIDATE_BOOLEAN) : false;    ...    $post = get_post( $template );    if ( 'publish' !== $post->post_status ) {         // only NON-published posts are gated        if ( ! current_user_can('manage_options') && get_current_user_id() !== (int) $post->post_author )            wp_send_json_error( 'Permission denied' );    }    $template_content = Helper_Functions::render_elementor_template( $template, $is_ID );  // renders & returns HTML    wp_send_json_success( array( 'template_content' => $template_content ) );}

Собственная модель Elementor считает элементы elementor_library непубличными: просмотр на фронтенде она ограничивает пользователями с edit_posts и делает это через block_template_frontend() на template_redirect. Этот AJAX-обработчик стоит рядом с той защитой и отрисовывает шаблон сам. Между тем для шаблонов библиотеки publish — это внутреннее состояние, а не заявление о том, что дизайн предназначен для публики. Здесь же этот статус оказывается единственным, что отделяет анонимного посетителя от отрисованного HTML любого шаблона библиотеки, в том числе тех, что никогда не размещались на публичной странице.

4. Как достигается дефект (концептуально)

Находка A. Достижимость держится на одном несоответствии. То, что проверяет обработчик, — действительный nonce pa-live-editor, — есть как раз у тех пользователей, которым до уязвимого участка доходить не следует. Nonce встраивается в редактор Elementor для каждого, кто способен его открыть, а открыть редактор на своём черновике позволяет лишь edit_posts — возможность участника. Барьер, на который полагается обработчик, тем самым преодолевается низкопривилегированным пользователем в рамках обычного, предусмотренного рабочего процесса. За барьером управление доходит до участка, который безусловно вставляет опубликованную запись. Недостающий шаг — тот, что делает update_template_title() и пропускает handle_live_editor(), — это вопрос «есть ли у пользователя manage_options?». Раз его никто не задаёт, участник выполняет публикацию, закреплённую ролевой моделью за редактором и администратором. Чтобы выйти на уязвимый путь, хватает обычного аутентифицированного доступа к редактору; детали на уровне запроса мы намеренно не приводим, пока идёт исправление.

Находка B. Тут нет и nonce, который нужно было бы добыть. Обработчик привязан к wp_ajax_nopriv_*, поэтому отвечает на анонимные запросы, а его единственная ветка, способная отказать, пропускается всякий раз, когда у целевой записи статус publish. Концептуальный разрыв в том, что «опубликованное» приравняли к «публичному». Для обычных записей это верно, но не для шаблонов библиотеки Elementor — класса объектов, который платформа сознательно держит за проверкой edit_posts. Поэтому неаутентифицированный актор выходит на путь «отрисовать и вернуть» для любого опубликованного шаблона библиотеки, просто сославшись на него. Неопубликованные шаблоны по-прежнему защищены, и это указывает прямо на причину — отсутствующую проверку возможности в ветке опубликованных записей. Здесь операционные детали запроса тоже ждут исправления.

5. Оценка воздействия

Находка A (CVSS ~5.4, Средняя). Учётная запись уровня участника создаёт произвольные опубликованные записи elementor_library со слагами по выбору злоумышленника и может повторять это в масштабе: несанкционированное создание контента, засорение базы данных и небольшой отказ в обслуживании за счёт массового создания. Потолок по-настоящему низкий. post_content пуст, заголовок следует фиксированному шаблону, meta_input — белый список, так что здесь нет ни хранимого XSS, ни удалённого выполнения кода. Честная сводка — нарушение контроля доступа среднего уровня: низкопривилегированный пользователь выполняет публикацию, зарезервированную за редактором и администратором. I:L/A:L отражает несанкционированное изменение целостности и неудобство массового создания, а PR:L — необходимость входа как участник.

Находка B (CVSS ~5.3, Средняя). Неаутентифицированное раскрытие содержимого шаблона библиотеки Elementor: дизайнов, встроенного текста, а также внутренних и подготавливаемых шаблонов, не связанных ни с одной публичной страницей, — всё это модель доступа Elementor ограничивает пользователями уровня участника и выше. Затрагивается только конфиденциальность (C:L, PR:N), без влияния на целостность или доступность, отсюда средне-низкий уровень.

6. Устранение

Находка A. Добавьте в handle_live_editor() проверку возможности, как у соседнего обработчика, и примените то же к pa_get_editor_template():

php
if ( ! current_user_can( 'manage_options' ) ) {    wp_send_json_error( 'Insufficient user permission' );}

Находка B. Перед отрисовкой добавьте проверку nonce и примените собственную политику просмотра Elementor, повторяя block_template_frontend():

php
check_ajax_referer( 'some-dedicated-nonce', 'security' );if ( ! \Elementor\User::is_current_user_can_edit( $template ) ) {   // i.e. edit_posts on the library item    wp_send_json_error( 'Permission denied' );}

Операторам сайтов стоит обновиться, как только производитель выпустит исправленную версию. До этого применяйте меры усиления из следующего раздела.

7. Обнаружение и усиление для владельцев сайтов

Полный рецепт эксплуатации не публикуется, поэтому признаки ниже выведены исключительно из механики:

  • Проверяйте содержимое elementor_library. Ищите опубликованные элементы библиотеки со слагами вида pa-dynamic-temp-* и заголовками по шаблону PA Template | #XXXX, особенно от учётных записей уровня участника. Внезапный всплеск таких записей — самый явный признак эксплуатации Находки A.
  • Проверяйте, у кого есть уровень участника и выше. Находке A нужен аутентифицированный участник, поэтому действуйте по принципу наименьших привилегий: удаляйте неактивные учётные записи участников, требуйте надёжной аутентификации и не раздавайте edit_posts шире необходимого.
  • Отслеживайте admin-ajax.php. Повторяющиеся вызовы действий handle_live_editor или get_elementor_template_content, причём второе — от неаутентифицированных клиентов, стоит журналировать и ограничивать по частоте.
  • Считайте шаблоны библиотеки чувствительными. Пока Находка B не исправлена, не храните в шаблонах библиотеки Elementor секреты, учётные данные и неопубликованные тексты; опубликованные элементы библиотеки могут быть прочитаны без аутентификации.
  • Виртуальное патчирование. Межсетевой экран веб-приложений или короткий must-use-плагин способны наложить проверку возможности на затронутые действия до выхода официального исправления — это концептуальный эквивалент недостающего current_user_can() перед обработчиками.
  • Держите плагин обновлённым и подпишитесь на журнал изменений производителя, чтобы исправленная версия ставилась без задержки.

8. Более широкий урок

Каждая конечная точка, которая меняет состояние или возвращает данные, обязана ответить на три независимых вопроса, и верный ответ на один не закрывает остальные:

  1. Подлинный ли запрос? — nonce / CSRF-токен.
  2. Тот ли это пользователь, за кого себя выдаёт? — аутентификация.
  3. Разрешено ли пользователю делать именно это? — возможность (current_user_can()).

В обеих находках ошибка одна: третий вопрос сводят к первому или пропускают совсем. Nonce легко достаётся любому, кто уже внутри обычного рабочего процесса продукта, и о привилегиях он не говорит ничего. А «опубликованное» — это статус, а не решение об авторизации; не позволяйте внутреннему состоянию объекта незаметно становиться его политикой доступа. Когда один обработчик защищён возможностью, а столь же мощный соседний — только nonce, эта несогласованность и есть уязвимость. Авторизуйте каждый уязвимый участок явно, прямо в нём.

9. Ответственное раскрытие

Эти находки обрабатываются в рамках скоординированного раскрытия. Производитель уведомлён в частном порядке и добросовестно. Полные технические детали Proof of Concept — точная последовательность запросов и значения полезной нагрузки — удерживаются из этой публичной статьи до появления исправления либо в течение 90 дней (смотря что наступит раньше), при согласовании сроков с производителем. Всё тестирование шло в изолированной, привязанной к localhost лаборатории против публичного GPL-плагина; к сторонним системам обращений не было, а тестовые плагины и пользователи были удалены после. Noroxi LLC проводит только авторизованные исследования безопасности и не помогает в эксплуатации действующих систем.

Источники

Опубликовано в защитных и образовательных целях. Noroxi LLC проводит только авторизованные исследования безопасности; операционные детали эксплуатации удерживаются до появления исправления.

Эксплуатируется на этой неделе · по понедельникам

Новые записи KEV, свежие PoC, разбор WordPress и что сделать на неделе. Без аккаунта.

Адрес используется только для этой рассылки и никому не передаётся.

Следующая статья

Обход 2FA в WP Hide & Security Enhancer 2.9.3 через «Настроить позже»