Аннотация. Login LockDown — плагин WordPress с более чем 100 000 активных установок. Среди прочих средств защиты он умеет добавлять на форму входа встроенную математическую CAPTCHA. В версии 2.18 проверка этой CAPTCHA устроена так, что сводит её на нет. Плагин отправляет ожидаемый ответ в браузер как клиентский токен, а потом проверяет вход, пересчитывая хеш над двумя значениями, и оба приходят из одного запроса. Пара «ответ/токен» не привязана ни к какому серверному состоянию, поэтому воспроизводится сколько угодно раз, и обещанная CAPTCHA защита от автоматизации попросту исчезает. Ниже — первопричина и защитное руководство для владельцев сайтов и разработчиков. По правилам координированного раскрытия полный рабочий proof of concept остаётся закрытым до выхода исправления.
Ключевые слова: Login LockDown, WordPress, обход CAPTCHA, захват-воспроизведение, CWE-294, CWE-804, обход средства защиты, защита от автоматизации.
1. Введение
Перебор паролей и подстановка учётных данных — самое постоянное давление на любую точку входа WordPress. Плагины вроде Login LockDown нужны, чтобы поднять стоимость автоматического подбора: блокировки по IP и, по желанию, CAPTCHA на форме входа и на входе WooCommerce.
Защитное средство, которое можно обойти, опаснее заведомо отсутствующего. Администратор, включивший встроенную CAPTCHA, считает, что автоматические попытки входа теперь упираются в проверку человека. Но если эту проверку достаточно открыть один раз и затем воспроизводить бесконечно, расчёт неверен: поверхность перебора, которую он считал закрытой, снова открыта, и без единого предупреждения.
Проблема сидит во встроенной математической CAPTCHA в Login LockDown 2.18 — на момент тестирования это был актуальный выпуск. Она завязана на недефолтную настройку: администратор должен выбрать встроенную CAPTCHA (captcha = builtin). Саму блокировку по IP мы проверили отдельно и признали корректной.
2. Затронутый компонент
| Плагин | Login LockDown — slug login-lockdown |
| Проверенная версия | 2.18 (актуальная/последняя на момент тестирования) |
| Активные установки | 100 000+ |
| Тип уязвимости | Обход средства защиты — недостаточная проверка CAPTCHA (захват-воспроизведение) |
| CWE | CWE-804 (Угадываемая CAPTCHA), CWE-294 (Обход аутентификации захватом-воспроизведением); родительский CWE-287 |
| Требуемые привилегии | Нет — без аутентификации, удалённо |
| Предусловие | Администратор установил параметр CAPTCHA на встроенную математическую CAPTCHA (captcha = builtin; не по умолчанию) |
| CVSS 3.1 | ~5.3 (Средняя) AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N |
| Статус | Свежая — без исправления; подтверждена в коде и воспроизведена в изолированной локальной лаборатории на актуальной версии |
3. Технический анализ
Встроенная математическая CAPTCHA просит посетителя решить небольшую сумму. Всё решает то, где лежит ожидаемый ответ, — а Login LockDown не держит его на сервере нигде. Нет ни записи сессии, ни временного значения (transient). Вместо этого форма входа выдаёт для каждой задачи два поля:
loginlockdown_captcha[<id>]— ответ, который вводит пользователь;loginlockdown_captcha_token[<id>]— скрытый токен, равныйwp_hash(<ответ>).
При отрисовке формы токен берётся прямо из ответа:
// libs/functions.php (login_form_fields — генерация токена, ~строка 244)
$output .= '<input ... name="loginlockdown_captcha['.$captcha_id.']" ...>';
$output .= '<input type="hidden" name="loginlockdown_captcha_token['.$captcha_id.']" value="'. wp_hash($captcha['value']) .'" />';При проверке wp_hash() пересчитывается над присланным ответом и сравнивается с присланным токеном:
// libs/functions.php (handle_captcha — проверка, ~строка 180)
foreach ($captcha_responses as $captcha_id => $captcha_val) {
if (wp_hash($captcha_val) === $captcha_tokens[$captcha_id]) { // ОБА операнда приходят из $_POST
return true;
} else {
return new WP_Error('lockdown_builtin_captcha_failed', ...);
}
}Оба операнда сравнения приходят из запроса. Проверка подтверждает только одно: отправитель знает некое значение и его wp_hash(), которое сам сервер и выдал мгновением раньше. Про текущее взаимодействие она не говорит ничего. Нет ни привязки к сессии, ни nonce, ни срока действия, ни пометки одноразовости. CAPTCHA должна гарантировать, что каждая задача решается один раз и сейчас; здесь пара (ответ, wp_hash(ответ)) становится переиспользуемым секретом.
Отсутствие состояния даёт два прямых следствия.
- Воспроизведение. Одна действительная пара
(ответ, токен), снятая с единственной легитимной загрузки формы, проходит проверку при любой последующей отправке — бессрочно. - Сбор полной таблицы. Пространство ответов крошечное (суммы в пределах 0–20), поэтому пары-тройки загрузок формы хватает на полную таблицу всех пар
(ответ, токен). Дальше решать задачи не нужно совсем.
4. Как достигается уязвимость (концептуально)
Чтобы добраться до уязвимой точки, аутентификация не нужна. Форма входа общедоступна, проверка выполняется на общедоступном POST-запросе входа. Неаутентифицированный посетитель запрашивает страницу входа как обычный браузер и читает согласованную пару (ответ, токен) прямо из возвращённого HTML. Ответ — это видимая математическая задача, токен лежит рядом скрытым полем. Токен — всего лишь wp_hash(ответ), а сервер не помнит, какие пары уже израсходованы, поэтому проверяющий не отличает свежую отправку человеком от сохранённой пары, которую подставляет скрипт.
Ворота перестают быть воротами. Это замок с ключом, отпечатанным на двери. Стоит узнать любую действительную пару — а она видна каждому, кто загрузит страницу, и во всём пространстве ответов таких значений лишь горстка — и шаг CAPTCHA сжимается до константы, которую автоматический клиент приклеивает к каждому запросу. Средство защиты от автоматизации превращается в формальность, а скриптовые попытки входа против wp-login.php идут так, будто CAPTCHA не настроена вовсе. Дословную последовательность запросов и значения мы опускаем намеренно. Защитнику, чтобы оценить риск, достаточно механизма выше.
5. Оценка воздействия
На практике форма входа теряет гарантию защиты от автоматизации. С выбранной встроенной CAPTCHA администратор рассчитывает, что автоматические попытки входа тормозит проверка человека. На деле одна снятая пара — или небольшая полная таблица — позволяет автоматическому клиенту проходить этот шаг при любом числе попыток. Перебор и подстановка учётных данных против входа WordPress и WooCommerce возвращаются, то есть ровно те атаки, ради защиты от которых плагин и ставят.
Честная оценка — Средняя. Вектор AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N (~5.3) описывает обход, достижимый по сети, с низкой сложностью и без аутентификации, без прямой потери конфиденциальности или доступности. Балл поставлен за влияние на целостность от нейтрализации средства защиты, а не за гарантированный захват учётной записи. Предусловие снижает оценку ещё: встроенная CAPTCHA по умолчанию выключена, и проблема касается только тех установок, где администратор включил её явно. Сам по себе обход аутентификацию не даёт — он лишь снимает слой перед аутентификацией.
Для контекста: отдельная проверка подтвердила, что логика блокировки по IP не затронута. Клиентский адрес берётся только из REMOTE_ADDR, а X-Forwarded-For/Client-IP не используются, поэтому обход блокировки подменой IP неприменим. Доступ к базе идёт через параметризованные запросы с белым списком столбцов, так что SQL-инъекции тоже нет.
6. Устранение
Правок две: перестать доверять клиенту ожидаемый ответ и сделать каждую задачу одноразовой. Ответу место на сервере — он там генерируется и хранится, в сессии или в недолговечном временном значении с ключом по сгенерированному сервером id, и удаляется ровно в момент проверки. Привычку отправлять wp_hash(ответ) в браузер и пересчитывать сравнение над двумя клиентскими значениями надо убрать. Задачу стоит привязать к сессии и ограничить сроком действия.
// Иллюстративный усиленный поток — сервер хранит ответ; клиент его никогда не видит.
// При отрисовке формы:
$captcha_id = wp_generate_uuid4();
$answer = /* вычислить ответ математической задачи на сервере */;
set_transient( 'lld_captcha_' . $captcha_id, (string) $answer, 5 * MINUTE_IN_SECONDS );
// выдавать только текст задачи и $captcha_id — никогда ответ или его хеш.
// При проверке:
$expected = get_transient( 'lld_captcha_' . $captcha_id );
delete_transient( 'lld_captcha_' . $captcha_id ); // одноразовость: потребить независимо от исхода
if ( $expected !== false && hash_equals( $expected, (string) $submitted_answer ) ) {
return true;
}
return new WP_Error( 'lockdown_builtin_captcha_failed', /* ... */ );До выхода исправленного выпуска администраторам стоит предпочесть встроенной математической CAPTCHA хорошо поддерживаемый сторонний вариант CAPTCHA или защиты от автоматизации и не полагаться на встроенную CAPTCHA как на единственную защиту от перебора.
7. Обнаружение и усиление для владельцев сайтов
- Пока считайте встроенную математическую CAPTCHA незащищающей. Если вы рассчитываете на защиту от автоматизации, держите блокировку по IP включённой (она корректна) и добавьте к ней вместо встроенного варианта поддерживаемую CAPTCHA с серверной проверкой или внешнюю задачу.
- Держите плагин обновлённым. Ставьте любой будущий выпуск Login LockDown, устраняющий это, и следите за журналом изменений поставщика.
- Применяйте наименьшие привилегии и надёжную аутентификацию. Реальный риск — возобновлённый перебор и подстановка учётных данных, поэтому надёжные уникальные пароли и двухфакторная аутентификация на привилегированных учётных записях гасят итоговый ущерб даже при обходе CAPTCHA.
- Ищите в журналах следы автоматизации. Поток POST-запросов входа с одними и теми же значениями полей ответа/токена CAPTCHA во множестве попыток — особенно с одного-двух адресов или при почти неизменном id задачи — это сигнатура воспроизводимой пары. Повторяющиеся неудачные входы на машинной скорости против
wp-login.phpзаслуживают того же внимания независимо от того, настроена CAPTCHA или нет. - Ограничивайте частоту и ставьте виртуальный патч на периметре. Правило WAF или обратного прокси, которое ограничивает частоту обращений к
wp-login.phpи помечает повторяющиеся одинаковые отправки токена CAPTCHA, работает как временный виртуальный патч. Привяжите правило к точке входа, чтобы не задеть легитимный трафик.
(Эти индикаторы выведены из механики уязвимости и не предполагают конкретного продукта или функции журналирования.)
8. Более общий урок
Принцип прост и нарушается постоянно: проверяющий не должен доверять клиенту нести секрет, который он обязан проверить. Как только проверяемое — ответ CAPTCHA, одноразовый код, цена, роль — уходит в браузер и возвращается обратно для сравнения, средство защиты становится декоративным. Авторитетное состояние безопасности принадлежит серверу: привязанное к конкретной сессии и взаимодействию, со сроком действия, потребляемое один раз, чтобы его нельзя было воспроизвести. Хеш клиентского значения — не состояние. Это то же клиентское значение, переодетое. Схемы «вызов–ответ» получают безопасность от свежести и одноразовости, а не от того, как трудно обратить хеш.
9. Ответственное раскрытие
Это исследование следует принципу координированного раскрытия. Поставщик уведомлён в частном порядке, а полная операционная техническая деталь — точная последовательность воспроизведения и значения полезной нагрузки — исключена из этой публичной статьи до выхода исправления или достижения согласованного срока. Ничто в этом материале не даёт работающей атаки.
Всё тестирование выполнялось Noroxi LLC только в изолированной, привязанной к localhost лаборатории на установке, которой мы управляем. Сторонние и производственные системы не затрагивались, а плагин был удалён сразу после тестирования. Noroxi LLC проводит только авторизованные исследования безопасности. Мы не тестируем системы без разрешения и не публикуем вооружённые детали против непропатченного ПО.
Ссылки
- CWE-294: Authentication Bypass by Capture-replay — https://cwe.mitre.org/data/definitions/294.html
- CWE-804: Guessable CAPTCHA — https://cwe.mitre.org/data/definitions/804.html
- CWE-287: Improper Authentication — https://cwe.mitre.org/data/definitions/287.html
- Справочник разработчика WordPress — Transients API — https://developer.wordpress.org/apis/transients/
- Справочник разработчика WordPress — Nonces — https://developer.wordpress.org/apis/security/nonces/
- OWASP — Credential Stuffing — https://owasp.org/www-community/attacks/Credential_stuffing
- FIRST — калькулятор CVSS v3.1 — https://www.first.org/cvss/calculator/3.1
Опубликовано в защитных и образовательных целях. Noroxi LLC проводит только авторизованные исследования безопасности; операционные детали эксплуатации не раскрываются до выхода исправления.
Эксплуатируется на этой неделе · по понедельникам
Новые записи KEV, свежие PoC, разбор WordPress и что сделать на неделе. Без аккаунта.