
Поиск следов эксплуатации CVE-2026-18963 (неаутентифицированный захват учётной записи Keycloak) в базе данных Keycloak
Скрипт psql, который ищет в базе данных Keycloak PostgreSQL следы
эксплуатации CVE-2026-18963 (захват учётной записи без аутентификации через
поток сброса учётных данных).
Опубликовано KYOS. Мы написали его во время установки патчей на развёртывания Keycloak, которые мы обслуживаем, чтобы убедиться, что в течение окна уязвимости никто не был захвачен.
CVE-2026-18963 — это недостаток в потоке сброса учётных данных
keycloak-services
(keycloak#51833).
Неаутентифицированный злоумышленник может завершить процесс сброса пароля для
любого пользователя, не переходя по ссылке подтверждения из письма, а затем
установить новый пароль для учётной записи. Взаимодействие с пользователем не
требуется.
Проверено на Keycloak 26.7.1 на одноразовом экземпляре. Мы не публикуем шаги воспроизведения; приведённые ниже подробности — те, что нужны для понимания запросов.
Поток отслеживает, на каком экране аутентификатора он находится, способом, не
привязанным к шагу, который его установил. Это позволяет повторно войти в поток
сброса учётных данных на шаге подтверждения по почте, где уязвимый
ResetCredentialEmail.action() возвращает успех, так и не проверив наличие
токена действия. Затем выполнение продолжается до формы обновления пароля.
Два следствия определяют каждый запрос в этом репозитории:
SEND_RESET_PASSWORD. Отсутствие этого события не является признаком.action() помечает адрес электронной почты учётной записи как
подтверждённый перед возвратом. Легитимный путь вообще не доходит до
action(), поэтому это изменение состояния, которое порождает только обход.
Q3 опирается на это.Исправление (keycloak#51844)
привязывает состояние экрана к выполнению, которое его установило, и позволяет
action() завершиться успешно только при наличии токена действия для именно
этого пользователя.
Уровень серьёзности: критический, CVSS v3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N),
согласно Red Hat и
NVD. Корневая причина —
некорректная проверка состояния в потоке аутентификации, исправлена в
keycloak#51844.
| Ветка | Исправлено в |
|---|---|
| 26.7.x | 26.7.2 (примечания к выпуску) |
| 26.6.x | 26.6.6 |
| 26.4.x (LTS) | 26.4.15 |
| 26.8 | 26.8.0 |
Всё ниже этих версий в поддерживаемых ветках уязвимо. Выпуски без поддержки (26.5.x, 26.3 и старше) исправлений не получат: считайте их скомпрометированными и обновитесь до исправленной ветки. Red Hat указывает legacy RH-SSO 7 как не подверженный; Red Hat Build of Keycloak 26.4/26.6 исправлен в 26.4.15-1 / 26.6.6-1.
Отключение самостоятельного сброса пароля устраняет уязвимую точку входа:
консоль администратора > Realm settings > Login > «Forgot password» выключено,
для каждого realm. Через admin API: PUT /admin/realms/{realm} с
{"resetPasswordAllowed": false}.
Это блокирует эндпоинт login-actions/reset-credentials, но также не даёт
легитимным пользователям сбрасывать собственный пароль, поэтому рассматривайте
это как временную меру до обновления. Учтите, что Red Hat не указывает
одобренного способа смягчения для этой CVE; установка патча — единственное
настоящее исправление.
Первая версия этого скрипта помечала завершённый сброс, непосредственно перед
которым не было SEND_RESET_PASSWORD.
Это было неверно, и
vilmosnagy указал на это
после воспроизведения CVE на своём собственном экземпляре и наблюдения, что событие всё равно появляется. Причина — первое следствие выше: аутентификатор почты работает
нормально, поэтому жертва получает настоящее письмо для сброса, а Keycloak регистрирует обычное
SEND_RESET_PASSWORD. Проверка была инвертирована. Она бы очистила каждую
скомпрометированную учётную запись, которую просматривала. Она удалена.
Схемы на основе таймингов имеют меньшую, но реальную проблему. Подход в
скрипте охоты thomasdelorge
сопоставляет SEND_RESET_PASSWORD с UPDATE_PASSWORD по общему code_id
и отмечает короткий промежуток. Ключ корреляции верен, и автор явно
указывает, что порог является эвристикой. Наше тестирование говорит то же самое: обойдённый сброс
и легитимный сброс, завершённый в том же браузере, порождают идентичные типы событий, идентичные ключи деталей и тот же code_id. Отличается только задержка,
что оставляет порогу нести весь вердикт. Любой, кто не спешит, остаётся
ниже него. Настоящие пользователи его превышают, потому что code_id остаётся тем же только когда
ссылка открывается в браузере, который начал процесс, а это также случай,
когда письмо, вероятно, уже открыто.
Мы не нашли ни события, ни детали события, ни ошибки, которые разделяют эти два случая. Нет события «шаг с почтой пропущен», которое можно было бы искать. Что существует — это побочный эффект состояния, и именно его использует Q3.
cve-2026-18963-keycloak-hunt.sql выполняет шесть запросов только для чтения:
| Запрос | Что он находит |
|---|---|
| Q0 | Сохраняет ли realm события вообще, их TTL и сохраняются ли детали событий. Q2 коррелирует по детали code_id, поэтому если детали отсутствуют, Q2 слеп, и его молчание ничего не значит. |
| Q1 | Каждую установку пароля-учётных данных внутри окна экспозиции (credential.created_date). Это и есть захват, и он переживает истечение срока событий. |
| Q2 | Каждое завершённое обновление учётных данных, сопоставленное с письмом сброса той же сессии аутентификации, и классифицированное. См. ниже. |
| Q3 | Детерминированный побочный эффект: пользователи, у которых email_verified без легитимной причины. |
| Q4 | Сбросы учётных данных через Admin-API и записи пользователей, чтобы объяснить строки Q1 и Q3 и исключить маршрут через администратора. |
| Q5 | Необработанная временная шкала событий сброса/учётных данных для ручного разбора отдельного случая. |
| Вердикт | Значение |
|---|---|
NO_MAIL_WAS_SENT | Обновление учётных данных разделяет code_id с неудачной отправкой: нет адреса у учётной записи или SMTP отклонил его. Ни одна ссылка не покидала сервер, поэтому никто не мог открыть её, но процесс всё равно дошёл до формы пароля. Легитимно невозможно; рассматривать как эксплуатацию. |
CLEARED_TOKEN_CLICKED | Сброс завершился под другим code_id, чем любой SEND_RESET_PASSWORD для этого пользователя. Только токен действия, использованный в другом браузере или сессии, даёт это, поэтому ссылка из письма действительно была нажата. Детерминированно легитимно. |
CANDIDATE | Тот же code_id, что и у отправки. Так выглядит обход, а также так выглядит легитимный сброс в том же браузере. Само по себе не является находкой. |
NOT_RESET_FLOW | Нет SEND_RESET_PASSWORD для этого пользователя в окне; пароль изменился каким-то другим способом (консоль учётной записи, admin API, регистрация). Сверьтесь с Q4. |
secs_after_send выводится для контекста. Не используйте его как вердикт.