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

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

WP Hide & Security Enhancer 2.9.3 завершает вход без второго фактора для пользователей с уже настроенной 2FA — через путь «Настроить позже» по умолчанию.

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

Аннотация. WP Hide & Security Enhancer (100 000+ активных установок) оборачивает вход в WordPress уровнем двухфакторной аутентификации (2FA). В версии 2.9.3 при конфигурации плагина по умолчанию обработчик проверки 2FA доверяет управляемому клиентом сигналу «Настроить позже» и завершает вход пользователю, который уже настроил второй фактор; код при этом не проверяется вовсе. Первопричину мы разбираем как учебный пример безопасного программирования в части управления состоянием аутентификации и даём владельцам сайтов конкретные шаги по обнаружению и усилению защиты. Находка свежая (0-day) и на момент написания не исправлена. Вендор уведомлён в рамках скоординированного раскрытия; полное рабочее доказательство концепции не публикуется до выхода исправления.

Ключевые слова: безопасность WordPress, обход двухфакторной аутентификации, нарушенная аутентификация, пропущенный критический шаг, CWE-287, CWE-304, безопасное программирование.

1. Чем опасен обход 2FA

Второй фактор отрабатывает своё именно в тот момент, когда первый уже не сработал. Когда злоумышленник стоит у формы входа с рабочим паролем — выуженным фишингом, повторно использованным, слитым или угаданным, — 2FA остаётся единственным действующим рубежом. Если тот же злоумышленник обходит и его, функция даёт спокойствие, но не защиту.

WP Hide & Security Enhancer — широко распространённый плагин усиления защиты WordPress, у которого более 100 000 активных установок. Одна из его возможностей перехватывает вход после шага с логином и паролем и выводит запрос второго фактора — код по электронной почте, код приложения-аутентификатора или код восстановления — прежде чем будет выдана аутентификационная cookie.

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

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

ПлагинWP Hide & Security Enhancer — slug wp-hide-security-enhancer
Проверенная версия2.9.3 (текущая/последняя на момент тестирования)
Активные установки100 000+
Тип уязвимостиОбход двухфакторной аутентификации / Нарушенная аутентификация
CWECWE-287 Неправильная аутентификация (CWE-304 Пропущенный критический шаг в аутентификации)
Требуемые привилегииНеаутентифицированный злоумышленник, обладающий основными учётными данными жертвы (логин + пароль). 2FA должна быть включена для роли жертвы при значении по умолчанию Enforce User to Configure 2FA = No.
Взаимодействие пользователяНет
CVSS 3.16.8 (Средняя) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, снижено из-за предусловия с учётными данными (триажёр может скорректировать)
СтатусСвежая (0-day), воспроизведена сквозным образом в изолированной лаборатории на текущей версии; на момент публикации без исправления

3. Анализ первопричины

После успешного шага с паролем плагин не выдаёт аутентификационную cookie сразу: он показывает собственную форму 2FA и передаёт вход выделенному обработчику. Замысел верный — ни одна cookie wordpress_logged_in не должна существовать, пока второй фактор не проверен.

Обработчик ломается на том, как он трактует возможность «Настроить позже». Этот путь предназначен для подлинной первичной регистрации: пользователю, которому 2FA только что стала обязательной, но который ещё не настроил ни одного варианта, можно разрешить пройти один раз на время настройки — если администратор не навязал обязательную настройку. Однако, решая, пропускать ли по этому пути, обработчик смотрит не на то состояние.

Соответствующая ветвь в modules/components/login_2fa.php, login_form_validate_2fa() (v2.9.3):

php
$_2fa_require_setup             = $this->wph->functions->get_module_item_setting('2fa_require_setup'); // default 'no'$other_2fa_option_require_setup = $this->_2fa_get_first_option_require_setup( $user->ID );
if ( strtolower( $submit ) == 'setuplater' && $_2fa_require_setup !== 'yes' ) {    // Only redirects if SOME OTHER active option still needs first-time setup:    foreach ( $this->active_2fa_options as $active_2fa_option_id => $_2fa_option ) {        if ( $active_2fa_option_id === '2fa_recovery_codes' ) continue;        if ( $_2fa_option->user_2fa_require_setup()            && ! $_2fa_option->user_2fa_option_setup_completed( $user->ID )            && $_2fa_id != $active_2fa_option_id ) {            wp_safe_redirect( $this->login_url( ... ) ); exit();        }    }    // No option needs setup -> log the user in WITHOUT verifying any 2FA code:    $this->login_success( $args );   // -> wp_set_auth_cookie()    exit;}

Где именно рвётся логика:

  • Ветвь ни разу не спрашивает, нужно ли текущему пользователю ещё настраивать 2FA. Она перенаправляет, только если незавершён какой-то другой вариант. Пользователь, чей собственный активный фактор уже работает, — не тот, для кого писали «Настроить позже», но код не различает их.
  • Для варианта с почтой user_2fa_require_setup() возвращает FALSE (а user_2fa_option_setup_completed() — TRUE); для варианта с приложением-аутентификатором user_2fa_option_setup_completed() возвращает TRUE после регистрации. Значит, у пользователя с настроенным или основным вариантом тело цикла не истинно никогда, и управление проваливается прямо в login_success().
  • login_success() вызывает wp_set_auth_cookie() вообще без проверки кода.
  • Отдельная защита authenticate_block_cookies(), подключённая к фильтру authenticate, обычно подавляет аутентификационную cookie, пока не пройдена 2FA. Срабатывает она лишь при выполнении этого фильтра, а путь запроса на проверку 2FA wp_authenticate не вызывает. Страховка поэтому не включается ни разу, и cookie, установленная login_success(), действительно доставляется.
  • Значение 2fa_require_setup по умолчанию — 'no' (modules/components/login_2fa_defaults.php), так что уязвимая ветвь активна «из коробки».

По сути это хрестоматийный пропущенный критический шаг (CWE-304). Решение о доверии отвечает на вопрос «нужно ли настраивать какой-то другой вариант?», тогда как значимый для безопасности вопрос звучит иначе: «проверен ли второй фактор этого пользователя или у него действительно ничего не настроено?». Для совершенно нового пользователя оба вопроса дают один ответ — потому путь и работает в задуманном случае — и расходятся ровно для тех, кого 2FA защищает, из-за чего он и даёт сбой.

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

Злоумышленнику нужно лишь то, что 2FA и так считает находящимся во вражеских руках: логин и пароль жертвы. Сделать это знание недостаточным — вся задача второго фактора.

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

Излом наступает шагом позже. Решая, «безопасно ли завершить вход без кода?», обработчик смотрит на состояние настройки других вариантов 2FA, а не на то, был ли действительно проверен собственный фактор этого пользователя. Если активный фактор жертвы не требует первичной настройки — почта, которая всегда считается настроенной, или ранее зарегистрированное приложение-аутентификатор, — предусловие выполнено навсегда. Ветвь «Настроить позже» заключает, что настраивать нечего, завершает вход и выдаёт cookie, не проверив ни одного кода.

Так второй фактор превращается в пустую операцию ровно для той аудитории, которую должен защищать: для тех, у кого он уже настроен. Код не угадывают, токен не крадут, гонку не выигрывают — обработчик просто доходит до точки «впустить его» по ветви, которая никогда не должна была открываться уже настроенной учётной записи. Пока плагин не исправлен, мы намеренно не публикуем конкретную последовательность запросов, значения параметров и команды воспроизведения.

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

При конфигурации по умолчанию всю защиту 2FA обойдёт любой, у кого есть пароль пользователя. Это и есть та модель угроз, ради которой 2FA разворачивают, — компрометация учётных данных через фишинг, повторное использование или утечку, — и механизм даёт сбой в единственном сценарии, ради которого его и ставили.

Затронута каждая учётная запись с включённой 2FA, включая администраторские. Успешный обход даёт полный аутентифицированный доступ к целевой учётной записи, а для администратора это по сути полный контроль над сайтом. Воздействие на конфиденциальность, целостность и доступность целевой учётной записи поэтому высокое.

Базовые метрики CVSS описывают уязвимость, достижимую по сети, с низкой сложностью, без взаимодействия и с высоким воздействием на C/I/A. Опубликованная оценка 6.8 (Средняя) отражает понижение из-за предусловия с учётными данными: злоумышленник уже должен обладать действительным паролем. Это предусловие — и есть причина, почему находка значима: обойти 2FA для злоумышленника, у которого есть пароль, — весь смысл функции. Триажёр может взвесить его иначе.

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

Поставьте решение о доверии в зависимость от состояния собственного второго фактора аутентифицируемого пользователя, а не от состояния настройки посторонних вариантов:

php
// Treat the user's own configured/primary option as "must be verified".// Only allow "Set up later" when the CURRENT user genuinely has NO usable,// configured 2FA option — not merely when some OTHER option is incomplete.
$current_option = $this->active_2fa_options[ $_2fa_id ] ?? null;
$user_has_usable_factor =    $current_option    && $current_option->user_2fa_option_setup_completed( $user->ID );
if ( strtolower( $submit ) == 'setuplater'     && $_2fa_require_setup !== 'yes'     && ! $user_has_usable_factor ) {    // genuine first-time enrolment only -> allowed to proceed    ...} else {    // a configured factor exists -> REQUIRE and verify its code before login_success()    ...}

Разработчикам и операторам:

  • В ветви setuplater разрешайте пропуск только тогда, когда у аутентифицируемого пользователя действительно нет пригодного настроенного варианта 2FA. Перед вызовом login_success() убедитесь, что у собственного активного/основного варианта пользователя ещё нет завершённой настройки: считайте настроенный вариант с почтой или приложением «настройка завершена» и требуйте его код.
  • Переданное клиентом поле submit=setuplater — не разрешение завершить вход пользователю, у которого уже есть работающий второй фактор. «Настроить позже» относится только к подлинной первичной регистрации, и проверять это нужно на стороне сервера.
  • Применяйте защиту аутентификационной cookie, подавляющую вход до прохождения 2FA, и на пути проверки 2FA, чтобы ни один обработчик не мог установить cookie в обход.
  • Обновитесь до исправленного выпуска, как только вендор его опубликует. До тех пор используйте варианты усиления защиты ниже.

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

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

  • Сделайте настройку 2FA обязательной. Уязвимая ветвь завязана на значение по умолчанию Enforce User to Configure 2FA = No. Установка Yes меняет поведение пути «Настроить позже» и в любом случае является разумным усилением; обязательная регистрация полезна сама по себе.
  • Отслеживайте входы, в которых второй фактор не был израсходован. В среде с включённой 2FA успешная аутентифицированная сессия без предшествующего проверенного кода с почты или из приложения аномальна. Сопоставляйте события выдачи запроса 2FA и успеха 2FA; вход, породивший аутентифицированную cookie без соответствующей проверки, стоит расследовать.
  • Настройте оповещения о неожиданных сессиях администратора. Новые входы администраторов из необычных мест, с необычными user-agent или в необычное время заслуживают пристального внимания — тем более пока дефект не исправлен.
  • Сократите то, что даёт украденный пароль. Ошибка помогает только злоумышленнику, у которого уже есть действительный пароль. Требуйте надёжные уникальные пароли, сбрасывайте учётные данные, которые, как известно или предполагается, повторно использовались или раскрыты, и держите число учётных записей с правами администратора небольшим.
  • Виртуальный патчинг / WAF. Межсетевой экран веб-приложений или слой фильтрации запросов в принципе может помечать или блокировать запросы к конечной точке проверки 2FA, которые пытаются завершить вход по сигналу «настроить позже», когда у учётной записи есть активный настроенный фактор. Рассматривайте это как временную меру, выигрывающую время, а не как замену исправлению кода.
  • Держите плагин обновлённым. Применяйте исправленный выпуск вендора оперативно, как только он появится, и подпишитесь на уведомления о безопасности плагина.

Ничто из перечисленного не является вымышленной возможностью продукта; всё выведено из механики дефекта и стандартной эксплуатационной практики WordPress.

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

Поток аутентификации обязан принимать окончательное решение о доверии исходя из состояния того шага, который он обеспечивает, а не соседнего, постороннего. «Разрешено ли пользователю завершить вход без второго фактора?» — это вопрос о втором факторе этого пользователя. Отвечать на него проверкой того, нужно ли настраивать какой-то другой вариант, — категориальная ошибка: верный ответ в доброкачественном случае и неверный — в состязательном.

Три привычки безопасного программирования уловили бы это:

  • По умолчанию запрещайте, а исключения вырезайте узко. Код по умолчанию «завершал вход» и перенаправлял лишь при определённом условии. Безопаснее — требовать проверку и пропускать её только при узко ограниченном, проверенном на сервере условии первичной регистрации.
  • Никогда не позволяйте переданному клиентом полю подменять решение об авторизации. submit=setuplater приходит из браузера. Трактовать его как разрешение завершить вход — значит отдать критичный для безопасности выбор в руки злоумышленника.
  • Делайте защиты безусловными на каждом пути, ведущем к точке назначения. Страховка в виде подавления cookie существовала, но не срабатывала там, где это важно. Механизм, работающий лишь на части маршрутов к точке назначения, — не механизм.

Код аутентификации — та часть приложения, где «работает в тестах» и «безопасно» легче всего спутать. Считайте критичной для безопасности каждую ветвь, способную выдать сессионную cookie, и проверяйте в ней сам обеспечиваемый шаг.

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

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

Вся проверка выполнена командой исследования безопасности Noroxi LLC на изолированной, привязанной к localhost тестовой установке. Никакие сторонние или промышленные системы не затрагивались, и, кроме нашей собственной лаборатории, в процессе не участвовала ни одна система. Сроки публикации дальнейших технических деталей и общедоступность исправления мы согласуем с вендором.

Источники

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

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

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

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

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

Отсутствие авторизации в WPS Hide Login (CWE-862)