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

SSRF в Happy Addons for Elementor 3.50.0 из-за разбора API-ключа (CWE-918)

Неочищенное поле Mailchimp API-ключа в Happy Addons for Elementor 3.50.0 позволяет участнику (Contributor) инициировать серверные запросы (CWE-918).

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

Аннотация. В Happy Addons for Elementor 3.50.0 (более 400 000 активных установок) есть уязвимость подделки серверных запросов (SSRF, CWE-918), доступная любому участнику (Contributor). Первопричина знакомая: значение, которое попадёт внутрь исходящего URL, очищается фильтром, оставляющим URL-метасимволы на месте, — выбранный очиститель не соответствует контексту, куда уходят данные. Разбираем дефектный код и корректное исправление как учебный пример безопасного программирования. По правилам координированного раскрытия шаги воспроизведения остаются у вендора; материал оборонительный и показывает, как распознавать, предотвращать и выявлять этот класс ошибок, а не как его использовать.

Ключевые слова: SSRF, CWE-918, очистка ввода, безопасность плагинов WordPress, Elementor, облачные метаданные, эшелонированная защита, минимальные привилегии.

1. Введение

Happy Addons for Elementor — набор виджетов для конструктора страниц Elementor, установленный более чем на 400 000 сайтах WordPress. При таких цифрах ставки меняются: один слабый строительный блок уезжает на каждый сайт, который добавит плагин, и одна ошибка наследуется в масштабе.

Подделка серверных запросов (SSRF, CWE-918) — один из самых разрушительных классов веб-уязвимостей, потому что превращает сервер приложения в прокси, говорящий изнутри границы доверия. Завладев этим прокси, злоумышленник дотягивается туда, куда интернет пускать не должны: до внутренних панелей администрирования, баз данных и, в первую очередь, до конечных точек облачных метаданных, раздающих краткоживущие учётные данные. Изучать этот случай стоит именно из-за его обыденности. Строка, очищающая ввод, делает ровно то, что заказал разработчик, — и при этом не делает ничего, чтобы остановить SSRF, потому что для этой задачи выбран не тот очиститель.

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

ПлагинHappy Addons for Elementor — slug happy-elementor-addons
Протестированная версия3.50.0 (актуальная на момент тестирования)
Активные установки400 000+
Тип уязвимостиПодделка серверных запросов (SSRF)
CWECWE-918 Server-Side Request Forgery
Требуемая привилегияУчастник (Contributor; любой пользователь с правом edit_posts; получает nonce редактора Elementor)
CVSS 3.1~6.5 (Средняя) AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L
СтатусНовая; подтверждена в коде. На момент публикации исправления нет — ведётся координированное раскрытие.

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

Аутентифицированное AJAX-действие ha_process_dynamic_select (wp_ajax_ha_process_dynamic_select) разрешает списки Mailchimp по переданному пользователем API-ключу. Оно защищено проверкой nonce (wp_verify_nonce('ha_editor_nonce')) и проверкой прав (current_user_can('edit_posts')). В ветке по умолчанию — не custom — ключ берётся из поля запроса global_api и проходит через sanitize_text_field():

php
// classes/select2-handler.php  (process_mailchimp_list)$choose_api  = ... sanitize_text_field($_REQUEST['mailchimp_api_choose']) ...;$global_api  = ! empty($_REQUEST['global_api'])   ? sanitize_text_field($_REQUEST['global_api']) : '';$custom_api  = ! empty($_REQUEST['mailchimp_api']) ? sanitize_key($_REQUEST['mailchimp_api'])     : $global_api; // ветка "custom" использует sanitize_key (безопасно)$current_api = $global_api;if ($choose_api == 'custom') { $current_api = $custom_api; }...$options = Mailchimp_Api::get_mailchimp_lists($current_api);

Всё решает то, какой очиститель отработает. sanitize_text_field() срезает теги и сворачивает пробелы, а URL-метасимволы вроде /, :, . и @ спокойно оставляет на месте. Для свободного текста это нормально. Но здесь значение — не свободный текст: оно уходит в построение исходящего URL. Правильный ответ уже показывает ветка custom: sanitize_key() пропускает только строчные буквенно-цифровые символы, дефисы и подчёркивания, и это закрыло бы дыру. Ветке global_api такой обработки не досталось.

Дальше по потоку get_mailchimp_lists() строит хост запроса прямо из сегмента «datacenter» ключа — всего, что идёт после первого -, — и отправляет серверный HTTP-запрос:

php
// widgets/mailchimp/mailchimp-api.php$server = explode('-', self::$api_key);if (!isset($server[1])) return 0;$url = 'https://' . $server[1] . '.api.mailchimp.com/3.0/lists';  // $server[1] под влиянием злоумышленника$response = wp_remote_post($url, ...);

Хост собирается конкатенацией строк из ввода, на который влияет злоумышленник. Поскольку очиститель выше по потоку оставил URL-метасимволы нетронутыми, сегмент «datacenter» может нести символы, меняющие то, как итоговая строка разбирается как URL, — классическая предпосылка SSRF.

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

API-ключ Mailchimp выглядит как <секрет>-<datacenter> (например, ...-us5), и плагин доверяет сегменту datacenter настолько, что вставляет его прямо в имя хоста. Код написан так, будто этот сегмент всегда остаётся коротким буквенно-цифровым кодом региона, а итоговый хост всегда оканчивается на .api.mailchimp.com.

Как только очиститель выше по потоку сохраняет URL-метасимволы, оба допущения рушатся. Пропустите в сегмент datacenter разделитель пути — и authority-часть URL (хост и необязательный порт, к которым клиент реально подключается) завершается раньше времени; добавляемый кодом суффикс .api.mailchimp.com больше не принадлежит хосту, а понижается в путь URL. Сервер подключается туда, куда указал автор ключа, а суффикс Mailchimp превращается в декоративный текст пути.

Чтобы добраться до обработчика, хватает уровня «Участник». Участник вправе открыть редактор Elementor на собственном черновике — именно там выдаётся nonce редактора, которого ждёт действие. Оба барьера — действительный nonce и право edit_posts — уже есть у обычного автора с низкими привилегиями. Ни администратора, ни взаимодействия с жертвой не нужно. Вдобавок часть полученного ответа возвращается разобранной как данные списка Mailchimp, поэтому фрагмент ответа может отразиться вызывающей стороне — и SSRF оказывается полуслепой, а не полностью слепой.

Мы намеренно не публикуем ни буквального значения ключа, ни тела запроса, ни готовой к копированию команды. Описанного выше механизма достаточно, чтобы понять проблему и защититься от неё, и недостаточно, чтобы превратить её в оружие.

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

Участник способен заставить сервер слать запросы к внутренним или внешним хостам и портам по своему выбору. На практике это:

  • Доступ к внутренним сервисам, отгороженным межсетевым экраном от публичного интернета, — интерфейсам администрирования, кэшам, базам данных.
  • Запросы к конечным точкам облачных метаданных (link-local сервисы метаданных), способным раскрыть учётные данные и конфигурацию экземпляра.
  • Картографирование и сканирование портов внутренней сети из доверенной точки.
  • Частичное вытягивание ответа через путь разбора списков (полуслепой SSRF).

Вектор CVSS AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L (≈6.5, Средняя) вбирает сетевую доступность, низкую сложность атаки, низкое, но ненулевое требование к привилегиям у участника, отсутствие взаимодействия с пользователем и ограниченное, но реальное воздействие на конфиденциальность, целостность и доступность. Удерживает оценку на Средней, а не Высокой, одна лишь необходимость аутентификации — барьер тонкий: многие сайты принимают регистрации участников или работают в многоавторных конфигурациях, где такие учётные записи уже есть в количестве.

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

Очищайте ключ так, чтобы URL-метасимволы не выживали, и проверяйте сегмент datacenter прежде, чем строить из него URL:

php
// Обрабатывайте ключ так же, как это уже делает ветка "custom":$global_api = ! empty($_REQUEST['global_api']) ? sanitize_key($_REQUEST['global_api']) : '';
// И/или явно проверяйте сегмент datacenter перед построением URL:$server = explode('-', $api_key);if (!isset($server[1]) || !preg_match('/^[a-z]+[0-9]+$/', $server[1])) {    return 0; // отклонить любой datacenter, содержащий URL-метасимволы}$url = 'https://' . $server[1] . '.api.mailchimp.com/3.0/lists';

Владельцам сайтов — до выхода исправленной версии:

  • Обновитесь до исправленной версии, как только её выпустит вендор, и следите за журналом изменений плагина.
  • Пересмотрите роли учётных записей. Уберите лишние учётные записи участников и авторов, отключите открытую регистрацию, если она не нужна, — уязвимость недостижима без аутентифицированной сессии с низкими привилегиями.
  • Если функция динамического выбора Mailchimp у вас не используется, подумайте, нужен ли плагин на этом сайте вообще.

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

  • Фильтрация исходящего трафика (egress). Самая действенная мера против SSRF — ограничить исходящий трафик с веб-сервера. Запретите веб-уровню доступ к диапазонам RFC 1918, localhost и особенно к link-local адресу метаданных; разрешите только те внешние хосты, которые сайту действительно нужны.
  • Усильте облачные метаданные. На облачных экземплярах требуйте усиленный/сессионный режим метаданных, чтобы одиночный неаутентифицированный GET не унёс учётные данные.
  • Минимальные привилегии. Назначайте каждому пользователю минимально нужную роль и проверяйте, у кого есть право edit_posts.
  • Мониторинг исходящих запросов. Следите по журналам сервера и egress-прокси за запросами от процесса WordPress к неожиданным хостам, внутренним диапазонам IP или конечной точке метаданных.
  • Виртуальное исправление / WAF. Правило, отклоняющее запросы к уязвимому AJAX-действию с полем API-ключа, содержащим URL-метасимволы, выигрывает время как виртуальный патч. Это временная мера, а не замена исправления кода.
  • Держите плагины обновлёнными и подпишитесь на ленты уязвимостей для используемых компонентов.

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

Повторяющаяся ошибка — относиться к очистке как к единому универсальному шагу, а не к контекстно-зависимому. sanitize_text_field() уместен для текста, который пойдёт на страницу; он неверен для значения, которое станет частью URL, пути файловой системы, команды оболочки или идентификатора SQL. Надёжная защита — валидация с учётом контекста вывода: решите, для чего предназначено значение, затем принудительно примените именно ту грамматику, которую допускает этот контекст, — здесь список разрешённого вида ^[a-z]+[0-9]+$ для кода datacenter.

Шире: как только ввод под влиянием пользователя конкатенируется в адрес назначения, к которому подключится сервер, считайте SSRF возможной — и держитесь этого, пока хост не проверен по списку разрешённого, а исходящий трафик не ограничен. Разбирать URL конкатенацией строк — хрупко. Строить его из проверенных компонентов, а затем разрешать и проверять итоговый хост перед подключением — вот что выдерживает нагрузку.

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

Мы ведём этот случай в рамках координированного раскрытия. Вендор уведомлён конфиденциально и добросовестно; полные технические детали и воспроизведение переданы ему напрямую, чтобы он мог подтвердить и исправить дефект. По стандартной практике полный proof-of-concept остаётся вне этого публичного материала до выхода исправления или до истечения разумного срока раскрытия. После выпуска исправленной версии мы обновим статью.

Все описанные здесь тесты проводились Noroxi LLC в изолированной, привязанной к localhost лабораторной среде против общедоступного GPL-плагина. Никакие сторонние системы не затрагивались, а плагин впоследствии был удалён из лаборатории.

Ссылки

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

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

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

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

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

Login LockDown 2.18: обход встроенной CAPTCHA воспроизведением токена