Аннотация. Плагин Maintenance для WordPress решает, снимать ли стену обслуживания, по cookie, которую контролирует сам посетитель, и ни разу не сверяет эту cookie с настроенным секретом. Достаточно любого непустого значения. Здесь разобраны класс уязвимости (нарушение контроля доступа, CWE-284/CWE-639), точная строка, где проверка уходит не туда, концептуальное объяснение, как до неё добирается неаутентифицированный запрос, и вытекающие отсюда шаги по исправлению и обнаружению. На момент написания плагин без исправления, поэтому рабочий proof-of-concept остаётся за пределами текста до выхода патча — цель в том, чтобы показать сам шаблон ошибки, а не выдать готовую атаку.
Ключевые слова: безопасность WordPress, нарушение контроля доступа, обход авторизации, проверка cookie, CWE-284, CWE-639, безопасная разработка.
1. Введение
Почти каждый сайт на WordPress рано или поздно проходит стадию обслуживания или «скоро открытие» — при сборке, переносе или намеренном выводе из сети — и до готовности владельца реальный фронтенд прячет за заглушкой отдельный плагин. Maintenance от WebFactory Ltd делает ровно это на более чем миллионе сайтов. Когда столь популярный плагин держит всю свою безопасность на единственной гарантии, любая трещина в ней заслуживает пристального взгляда.
Сама гарантия узкая: посетитель без привилегий видит страницу обслуживания и ничего больше. Она рушится, едва решение «пропустить этого посетителя» начинает зависеть от значения, которое посетитель волен задать как угодно. Доверять токену от клиента так, будто сервер его проверил, — хрестоматийное нарушение контроля доступа, частое и наглядное, и уже поэтому его стоит описать, пока патча ещё нет.
2. Затронутый компонент
| Поле | Значение |
|---|---|
| Плагин | Maintenance |
| Slug | maintenance |
| Производитель | WebFactory Ltd |
| URL плагина | https://wordpress.org/plugins/maintenance/ |
| Протестированная / Затронутая версия | 4.32 (последняя на момент тестирования, 2026-10-05); уязвимая логика существует давно и, весьма вероятно, присутствует в более ранних версиях, содержащих ту же проверку cookie skip_maintenance_mode |
| Активные установки | 1 000 000+ |
| Тип уязвимости | Нарушение контроля доступа — обход режима обслуживания |
| CWE | CWE-284 (Ненадлежащий контроль доступа) / CWE-639 (Обход авторизации через управляемый пользователем ключ) |
| Требуемая аутентификация | Нет (без аутентификации) |
| CVSS 3.1 (предложено) | 5.3 Средний — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| Статус | Без исправления (0-day на момент публикации) |
3. Технический анализ
Режим обслуживания держится на хуке template_include с очень низким приоритетом, который подставляет шаблон обслуживания каждому посетителю, кроме администраторов:
// line ~104
add_action('template_include', array($this, 'template_include'), 999999);Есть и функция «Direct Access Link» (ссылка прямого доступа). Владелец задаёт секретный токен (direct_access_link); посетитель, предъявивший этот секрет в параметре строки запроса, проходит, и плагин ставит cookie skip_maintenance_mode, чтобы исключение держалось 24 часа. С этим всё в порядке. Беда в том, как исключение проверяется на последующих запросах.
// Внутри load_maintenance_page() (начинается ~строка 4331)
// строка ~4346 — cookie УСТАНАВЛИВАЕТСЯ только при предъявлении секретного direct_access_link
$custom_url_link = $mtnc_options['direct_access_link'];
if (!empty($custom_url_link)) {
if (isset($_GET[$custom_url_link])) {
setcookie("skip_maintenance_mode", true, time() + 24 * 60 * 60, '/');
$_COOKIE['skip_maintenance_mode'] = true;
}
}
// ... позже ПРОВЕРКА обхода (строка ~4421) доверяет «сырой» клиентской cookie:
if (!empty($_COOKIE['skip_maintenance_mode'])) {
$show_maintenance_page = 0; // ОШИБКА: наличие ЛЮБОГО значения cookie снимает стену
}Путь записи ведёт себя правильно: cookie появляется только после верного секрета. Разваливается путь чтения — решение об авторизации целиком держится на !empty($_COOKIE['skip_maintenance_mode']). Это значение ни разу не сравнивают с direct_access_link, не подписывают и не привязывают к серверной сессии или хранимому состоянию. Cookie — это насквозь клиентские данные, и плагин, принимающий само наличие cookie за доказательство авторизации, тем самым отдаёт решение клиенту: теперь клиент сам решает, авторизован ли клиент.
Это CWE-639 (обход авторизации через управляемый пользователем ключ) поверх CWE-284 (ненадлежащий контроль доступа). Конструкция с секретным токеном здравая; падает она потому, что шаг проверки смотрит не на то свойство — есть ли значение, — вместо криптографического равенства секрету.
4. Как достигается уязвимость (концептуально)
Чтобы добраться до сломанной проверки, не нужны ни привилегии, ни учётная запись, ни знание секрета. Фильтр обслуживания срабатывает на обычных фронтенд-запросах анонимных посетителей — в этом вся его работа, — и на каждом из них плагин читает cookie skip_maintenance_mode, выбирая между заглушкой и живым сайтом.
Поскольку HTTP-клиент распоряжается отправляемыми cookie, а проверка пропускает любое непустое значение, вопрос, на который плагин думает, что отвечает («предъявлял ли этот посетитель верный секрет раньше?»), тихо превращается в вопрос, на который посетитель отвечает сам себе («хочу ли я войти?»). Вся уязвимость — в этом разрыве между защищённой записью и незащищённым чтением: секрет оберегает лишь удобство автоматической установки cookie, но не акт её признания. Посетитель, не видевший секрета ни разу, попадает в то же состояние «пропущен», просто заявив о нём.
Запрос, который это демонстрирует, мы намеренно опускаем. Описанного механизма хватает, чтобы оценить риск и проверить исправление; готовая к копированию атакующая строка не добавляет ничего и остаётся закрытой до выхода исправленной версии.
5. Оценка воздействия
Неаутентифицированный злоумышленник проходит стену обслуживания и видит живой фронтенд-контент, который администратор хотел скрыть, — сайт перед запуском, незавершённую сборку или площадку, снятую из сети намеренно. Ровно от этого плагин и должен защищать.
Радиус поражения ограничен. Обход раскрывает лишь то, что WordPress и так отдал бы фронтенд-посетителям после снятия стены; сам по себе он не даёт доступа к wp-admin, настройкам плагина или базе данных и ничего не меняет. Именно этот характер — только чтение, только конфиденциальность — и ставит предложенный CVSS на 5.3 Средний (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N): доступно по сети, низкая сложность, без привилегий и взаимодействия, область не меняется, ограниченное воздействие на конфиденциальность, без воздействия на целостность и доступность. И всё же для бизнеса цена может быть ощутимой, когда скрытый контент чувствителен, — невыпущенные продукты, данные staging или сайт, снятый как раз для того, чтобы на него не смотрели.
6. Исправление
Проверяйте значение cookie, а не доверяйте тому, что она есть. Признавайте обход только при совпадении значения cookie с настроенным секретом, сравнивая в постоянном времени:
if (
!empty($mtnc_options['direct_access_link'])
&& !empty($_COOKIE['skip_maintenance_mode'])
&& hash_equals((string) $mtnc_options['direct_access_link'], (string) $_COOKIE['skip_maintenance_mode'])
) {
$show_maintenance_page = 0;
}А когда приходит верный параметр запроса direct_access_link, задавайте cookie значение самого секрета — или, лучше, сгенерированного на сервере подписанного токена — вместо true. hash_equals() убирает тайминговые побочные каналы при сравнении.
Владельцам сайтов стоит перейти на исправленную версию, как только вендор её выпустит. До тех пор действуют меры усиления защиты ниже.
7. Обнаружение и усиление защиты для владельцев сайтов
Каждый пункт здесь прямо следует из механики выше:
- Считайте режим обслуживания сокрытием (obscurity), а не контролем доступа. Контенту, который действительно должен оставаться скрытым, место за слоем, недоступным посетителю, — HTTP Basic Auth, список разрешённых IP, правило межсетевого экрана или непубличный хост, — а не за одним фронтенд-плагином.
- Следите за характерной формой запроса. Фронтенд-запрос с cookie
skip_maintenance_mode, которому не предшествовал запрос с секретным параметромdirect_access_link, подозрителен. Логи веб-сервера и WAF способны выявить запросы, приходящие уже с cookie обхода. - Минимум привилегий: отключите функцию. Если «Direct Access Link» вы не используете, оставьте её ненастроенной и никогда не полагайтесь на неё для защиты чего-либо чувствительного.
- Виртуальное патчирование. Как временная мера, WAF или правило на периметре может удалять или игнорировать cookie
skip_maintenance_mode, пришедшую без проверенного секрета, пока не установлено официальное исправление. Это смягчение, а не лечение. - Держите плагин обновлённым и подпишитесь на примечания к выпускам вендора, чтобы без промедления поставить исправленную версию.
8. Более широкий урок
Наличие — не доказательство. Исключение на основе токена надёжно ровно настолько, насколько надёжно сравнение, которое его признаёт. Всякий раз, когда сервер принимает решение об авторизации по данным от клиента — cookie, заголовку, параметру запроса, скрытому полю формы, — он обязан сверять значение с серверным секретом или серверным состоянием, использовать для секретов сравнение в постоянном времени и никогда не выводить авторизацию из одного лишь наличия поля. Защитить запись и оставить открытым чтение — ловушка, которая повторяется снова и снова. Свойство безопасности живёт в проверке, а не в том, как значение было установлено в первый раз.
9. Ответственное раскрытие
Проблема обрабатывалась в рамках скоординированного раскрытия. Вендор уведомлён в частном порядке и добросовестно, а полный рабочий proof-of-concept остаётся за пределами этой общедоступной статьи до выхода исправления. Полные технические детали мы опубликуем только после выхода исправленной версии либо по истечении согласованного окна раскрытия, согласуя сроки с вендором. Вся проверка велась на локальном изолированном экземпляре WordPress под контролем исследователя; никакие сторонние или рабочие системы не тестировались и не затрагивались.
Ссылки
- CWE-284: Improper Access Control — https://cwe.mitre.org/data/definitions/284.html
- CWE-639: Authorization Bypass Through User-Controlled Key — https://cwe.mitre.org/data/definitions/639.html
- OWASP: Broken Access Control — https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- WordPress Plugin Developer Handbook — Nonces — https://developer.wordpress.org/apis/security/nonces/
- WordPress Plugin Developer Handbook — Checking User Capabilities — https://developer.wordpress.org/apis/security/user-roles-and-capabilities/
- FIRST CVSS v3.1 Calculator — https://www.first.org/cvss/calculator/3.1
Опубликовано в оборонительных и образовательных целях. Noroxi LLC проводит только авторизованные исследования безопасности; операционные детали эксплуатации не раскрываются до выхода исправления.
Эксплуатируется на этой неделе · по понедельникам
Новые записи KEV, свежие PoC, разбор WordPress и что сделать на неделе. Без аккаунта.