Keycloak 계정 탈취 취약점(CVE-2026-18963)
■ 서론
2026년 8월, 여러 애플리케이션의 사용자 인증을 통합 관리하는 오픈소스 인증 플랫폼 Keycloak에서 계정 탈취가 가능한 취약점(CVE-2026-18963)이 공개됐다. 해당 취약점의 심각도는 CVSS 9.1로 평가되었다. 이 취약점은 비밀번호 재설정 과정에서 이메일을 통한 본인 확인 단계를 우회할 수 있다. 이를 악용하면 공격자는 피해자의 이메일에 접근하지 않고도 인증 절차를 건너뛰어 임의 계정의 비밀번호를 재설정 및 계정 탈취가 가능하다.
Keycloak은 SSO(Single Sign-On)를 중심으로 OAuth 2.0, OpenID Connect, SAML 등 표준 프로토콜 기반의 인증·인가와 사용자 계정 관리 기능을 제공한다. GitHub Star 약 3만 6천개를 보유할 만큼 널리 알려져 있으며, 개인뿐 아니라 기업과 기관의 서비스 인증 기반으로 폭넓게 사용된다.
그림 1. Keycloak GitHub Star
이 취약점은 취약 버전의 Keycloak을 통해 비밀번호 찾기 기능이 활성화된 환경에서 악용된다. 이 기능은 로그인 페이지에 노출되어 있어, 공격자는 인증 없이 대상의 사용자명이나 이메일 주소만으로 관리자 계정을 포함한 임의 계정 탈취가 가능하다. 따라서 해당 환경에서는 취약 버전 사용 여부를 확인하고 수정된 버전으로 업그레이드해야 한다.
■ 영향받는 소프트웨어 버전
CVE-2026-18963에 취약한 소프트웨어는 다음과 같다.
| S/W 구분 | 취약 버전 |
|---|---|
| Keycloak | 26.0.0 <= version < 26.4.15 26.5.0 <= version < 26.6.6 26.7.0 <= version < 26.7.2 |
| Red Hat Build of Keycloak | 26.4.0 <= version < 26.4.15 26.6.0 <= version < 26.6.6 |
■ 공격 시나리오
그림 2. 공격 시나리오
① 공격자는 피해자 계정의 비밀번호 재설정을 요청한다. ② 공격자는 이전 요청에서 인증 세션에 남은 상태값을 악용해 이메일 인증을 우회한다. ③ 우회에 성공한 공격자는 피해자 계정의 새 비밀번호를 설정한다. ④ 공격자는 변경한 비밀번호로 피해자 계정에 로그인하여 계정을 탈취한다. |
|---|
■ 테스트 환경 구성 정보
피해자 서버는 취약한 Keycloak 26.7.1을 Docker 컨테이너로 구동한다. 공격자는 피해자 서버에 접근 가능한 호스트라면 어디서든 공격할 수 있으며, 해당 테스트에서는 동일 네트워크상의 Kali Linux 컨테이너를 사용한다.
| 이름 | 정보 |
|---|---|
| 피해자 | Keycloak 26.7.1 (172.17.0.2:9105) |
| 공격자 | Kali Linux (172.17.0.3) |
■ 취약점 테스트
Step 1. 취약 환경 구성
먼저 피해자 환경을 구성한다. 다음 명령어로 CVE-2026-18963에 취약한 Keycloak 26.7.1을 로그인에 사용하는 웹사이트 컨테이너를 구동한다.
> git clone https://github.com/EQSTLab/CVE-2026-18963.git > cd CVE-2026-18963 > docker build -t cve-2026-18963 . > docker run -d --name cve-2026-18963 -p 9105:9105 cve-2026-18963 |
|---|
http://172.17.0.2:9105을 통해 로그인 기능이 있는 웹 페이지에 접근한다.
그림 3. 공격 대상 웹 페이지 확인
로그인 버튼을 누르면 Keycloak 로그인 페이지로 이동하며, 이 웹사이트가 인증을 Keycloak에 위임하고 있음을 확인할 수 있다. 이때 로그인 페이지에 공격에 사용되는 비밀번호 찾기 기능이 활성화되어 있는 것도 드러난다.
그림 4. 피해자 Keycloak 로그인 페이지
Step 2. 공격 수행 및 결과 확인
공격자는 다음과 같이 임의 계정의 비밀번호를 변경하는 스크립트를 실행한다.
> git clone https://github.com/EQSTLab/CVE-2026-18963.git > cd CVE-2026-18963 > python3 exploit.py [username] [new-password] --url http://[target-ip]:9105 |
|---|
그 결과 입력한 계정의 비밀번호가 바뀌어, 공격자가 피해자 계정을 탈취하게 된다.
그림 5. 피해자 계정 탈취
■ 취약점 상세 분석
Step 1. 공격 흐름
이 취약점은 비밀번호 재설정 과정에서 인증 수단 선택 화면 표시 여부를 기록하는 상태값이 해당 단계 종료 후에도 정리되지 않고 다른 인증 단계까지 남아 악용되면서 발생한다. 공격 흐름을 이해하기 위해 먼저 Keycloak의 비밀번호 재설정 단계와 공격에 사용되는 주요 상태값을 살펴본다.
Keycloak의 비밀번호 재설정은 계정 입력, 메일을 통한 본인 확인, 새 비밀번호 설정 순으로 진행된다. 각 단계를 실행 단계(Execution)라 하고, 단계마다 실행 단계 ID(Execution ID)가 붙는다. 주요 실행 단계는 다음과 같다.
| 순서 | 정보 | 정보 |
|---|---|---|
| 1 | reset-credentials-choose-user (계정 입력 단계) | 비밀번호 재설정 대상 계정을 입력 |
| 2 | reset-credential-email (메일 발송 단계) | 비밀번호 재설정 링크 전송 및 본인 여부 검증 |
| 3 | reset-password (비밀번호 재설정 단계) | 새 비밀번호를 설정 |
각 단계는 앞 단계가 성공적으로 완료돼야 다음 단계로 넘어간다. 처리 및 검증은 인증 세션(authentication session)에 저장된 단계별 진행 상태값을 기준으로 수행된다. 인증 세션은 로그인, 비밀번호 재설정 등 인증 흐름을 진행하는 동안 유지되는 브라우저 탭 단위 세션으로, URL의 tab_id로 식별된다. 서버는 요청마다 tab_id로 세션을 찾아, 그 안에 저장된 상태값을 근거로 지금 어느 단계이고 다음에 무엇을 해야 할지 판단한다.
이번 취약점에서 악용되는 상태값은 다음과 같다.
| 상태값 | 의미 |
|---|---|
| current.authentication.execution | 현재 처리 중인 실행 단계의 ID |
| auth.selector.screen.rendered | 인증 수단 선택 화면의 표시 여부 |
auth.selector.screen.rendered가 나타내는 인증 수단 선택 화면은 현재 실행 단계에서 진행할 수 있는 동작을 사용자에게 제시하고 그중 하나를 선택받는 화면이다. 아래에서는 PoC의 요청을 순서대로 따라가며 계정 입력 단계에서 생성된 이 상태값이 메일 발송 단계까지 어떻게 이어지는지 확인한다.
그림 6. 공격 흐름
① 서버 세션의 인증 수단 선택 화면 상태값 생성 유도
비밀번호 재설정 폼에 tryAnotherWay=on을 전송하고, 이후 같은 인증 세션을 다시 사용하기 위해 tab_id를 복사해둔다.
그림 7. tryAnotherWay 전송
tryAnotherWay=on을 처리하기 전 인증 세션에는 진행 단계 관련 값만 존재한다.
그림 8. 요청 처리 전 인증 세션
요청을 처리한 뒤에는 인증 세션에 auth.selector.screen.rendered = "true"가 저장되어, 서버가 인증 수단 선택 화면을 표시해야 하는 상태임을 기억한다.
그림 9. 요청 처리 후 인증 세션
② 비밀번호 재설정 대상 지정 및 세션 유지
표시된 인증 수단 선택 화면에서 항목을 선택해 요청을 제출할 때, POST 본문에 비밀번호를 재설정할 대상인 username=victim을 추가하고 authenticationExecution 필드는 제외한다. 정상적인 제출과 달리 이 필드를 빼면, 앞서 저장된 인증 수단 선택 화면 상태값이 제거되지 않고 세션에 그대로 유지된다.
그림 10. authenticationExecution 삭제 후 username 전송
서버는 이 제출을 계정 입력 단계의 처리로 받아 username=victim을 재설정 대상으로 저장한다.
그림 11. 인증 세션에 저장된 재설정 대상
이어서 흐름은 메일 발송 단계로 진행되어 재설정 메일이 발송되고, 세션의 현재 단계도 메일 발송 단계로 갱신된다. 이때 인증 수단 선택 화면 상태값은 계정 입력 단계에서 저장된 채 제거되지 않은 채 남아 있다
③ 비밀번호 재설정 흐름 재진입
저장해 둔 tab_id로 비밀번호 재설정 페이지에 다시 접근하여, 앞선 요청에서 사용한 동일한 인증 세션을 불러온다.
그림 12. tab_id로 조작된 서버 세션을 다시 호출
이 세션에는 인증 수단 선택 화면 상태값과 메일 발송 단계를 가리키는 현재 실행 단계 ID가 남아 있다. 서버는 이 값을 기반으로 인증 수단 선택 화면을 생성하는데, 현재 단계에서 택할 수 있는 동작이 메일 발송뿐이라 메일 발송 선택지가 담긴 선택 화면이 만들어진다.
그림 13. 메일 발송 단계의 인증 수단 선택 화면
④ 메일 링크 확인 없이 비밀번호 재설정
메일 발송 단계 인증 수단 선택 화면의 폼에서도 authenticationExecution을 삭제하고 전송한다.
그림 14. authenticationExecution 삭제 후 전송
정상적인 비밀번호 재설정에서는 메일로 전달된 링크를 통해 계정 소유 여부를 확인해야 다음 단계로 진행할 수 있다. 하지만 생성된 인증 수단 선택 화면의 폼을 제출하면 메일 링크를 확인하지 않았는데도 메일 발송 단계가 통과되어 비밀번호 재설정 화면이 표시된다.
그림 15. 메일 링크 확인 없이 도달한 비밀번호 재설정 화면
이 화면에서 새 비밀번호를 설정하면 victim 계정의 비밀번호가 실제로 변경되며, 변경한 비밀번호로 로그인이 가능하다.
Step 2. 취약 코드 분석
앞선 공격 흐름에서는 계정 입력 단계에서 생성된 인증 수단 선택 화면 상태값이 제거되지 않은 채 메일 발송 단계까지 유지되었고, 현재 실행 단계와 결합해 메일 발송 단계의 인증 수단 선택 화면을 생성했다. 이렇게 생성된 화면의 폼을 제출하면 메일 링크 확인 없이 비밀번호 재설정 단계까지 진행할 수 있었다. Step 2에서는 이러한 동작이 가능했던 원인을 취약 지점을 중심으로 살펴본다.
① 인증 수단 선택 화면 상태값의 실행 단계 미구분
공격자가 인증 흐름에서 tryAnotherWay=on을 전송하면, 서버는 인증 세션에 auth.selector.screen.rendered(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED) 상태를 저장한다.
그림 16. tryAnotherWay 요청 시 인증 수단 선택 화면 표시 상태 저장
이때 저장되는 값은 true뿐이며, 해당 상태가 어느 실행 단계에서 생성되었는지는 함께 저장되지 않는다. 따라서 계정 입력 단계에서 생성된 값과 메일 발송 단계에서 생성된 값을 상태값 자체만으로 구분할 수 없다.
서버는 값이 true이면 별도 상태값인 current.authentication.execution(CURRENT_AUTHENTICATION_EXECUTION)을 읽어 현재 실행 단계에 맞는 인증 수단 선택 화면을 생성한다.
그림 17. 현재 실행 단계 기반 인증 수단 선택 화면 구성
따라서 이 상태값이 지워지지 않고 유지되면, 처음 이 값을 생성한 실행 단계가 끝난 뒤에도 현재 실행 단계에 맞는 인증 수단 선택 화면을 생성하는 데 재사용될 수 있다.
② 요청 필드에 의존하는 상태값 관리 방식
인증 수단 선택 화면을 정상적으로 제출하면 hidden 필드인 authenticationExecution(authExecId)이 함께 전송된다. 이 값은 사용자가 선택한 실행 단계를 서버에 알리는 값으로, 다음 단계로 넘어간다고 판단하여 auth.selector.screen.rendered를 삭제한다.
하지만 이 값을 제외하고 보내면, auth.selector.screen.rendered를 정리하는 분기를 건너뛰어, 인증 수단 선택 화면 표시 상태가 제거되지 않고 이후 실행 단계에서도 그대로 남는다.
그림 18. authenticationExecution 유무에 따른 분기
즉 공격자는 authenticationExecution만 빼고 요청을 보내, 앞서 만든 인증 수단 선택 화면 표시 상태를 이후 실행 단계까지 유지할 수 있다.
③ 비정상적인 흐름을 통한 이메일 인증
정상적인 비밀번호 재설정 흐름에서 메일 발송 단계는 메일 링크를 통해 계정의 소유자임을 인증하며, 사용자가 제출하는 별도의 폼이 없다. 이 단계에는 폼 제출을 처리하는 action()이 존재하지만 정상 흐름에서는 호출되지 않는다. 그런데 이 action()은 아무런 검증 없이 단계를 그대로 성공 처리하므로, 호출되기만 하면 메일 링크 확인 없이 단계가 통과된다.
그림 19. 이메일 인증 없이 성공 처리되는 action()
인증 수단 선택 화면 표시 상태값이 세션에 남아 있으면 메일 발송 단계에서도 인증 수단 선택 화면이 만들어진다. 이 폼을 제출하면, 해당 단계의 action()이 실행되며 이메일을 통한 인증 없이 성공 처리된다.
그림 20. 현재 실행 단계의 action() 호출
이 세 결함이 맞물리며 공격자는 메일 링크 없이도 메일 발송 단계를 통과할 수 있다. 그 결과 계정 소유 확인 없이 비밀번호 재설정 단계에 이르러 대상 계정의 비밀번호를 임의로 변경할 수 있다.
■ 대응 방안
CVE-2026-18963은 Keycloak 26.4.15, 26.6.6, 26.7.2에서 수정되었다. 해당 보안 패치에서는 서버 인증 세션에 남는 인증 수단 선택 화면 상태값을 실행 단계 단위로 관리하고, 메일 발송 단계에서 계정 소유 여부를 확인하도록 하여 우회 경로를 차단하였다.
| S/W 구분 | 패치 버전 |
|---|---|
| Keycloak | 26.4.15 / 26.6.6 / 26.7.2 이상 |
| Red Hat Build of Keycloak | 26.4.15 / 26.6.6 이상 |
Step 1. Keycloak 보안 패치 적용
1. 인증 세션의 상태값 관리 방식 변경
앞서 분석에서 본 것처럼, 취약 버전은 인증 수단 선택 화면 상태값(auth.selector.screen.rendered)에 true만 저장해 어느 실행 단계에서 만든 값인지 구분하지 못했다. 그래서 흐름이 다른 실행 단계로 넘어간 뒤에도 이 값이 그대로 재사용됐다. 보안 패치에서는 이 값에 true 대신 인증 수단 선택 화면을 만든 실행 단계의 ID를 저장하도록 수정하였다.
그림 21. 인증 수단 선택 화면 상태값에 실행 단계 ID 저장
이렇게 상태값이 실행 단계와 함께 저장되면서, 서버는 상태값에 저장된 실행 단계와 현재 진행 중인 실행 단계를 비교할 수 있게 되었다. 두 단계가 다르면 해당 값을 삭제하므로, 다른 실행 단계에서 생성된 인증 수단 선택 화면 상태값이 비밀번호 재설정 흐름까지 이어지는 문제가 해소됐다.
그림 22. 저장된 단계와 현재 단계 일치 여부 확인
2. 발송 단계 action()에 이메일 인증 확인 추가
이 단계의 action()은 정상 흐름에서 호출될 일이 없는 경로여서, 취약 버전에서는 별도 검증 없이 단계를 성공 처리하기만 했다. 하지만 세션 상태값 문제로 이 경로가 호출될 수 있음이 드러났고, 정상 흐름이 메일 링크로 수행하던 계정 소유 확인을 이 경로에도 동일하게 적용했다.
그림 23. 비밀번호 재설정 전 이메일 인증 여부 확인
이 두 변경으로 다른 실행 단계에서 만들어진 서버 세션의 상태값을 악용할 수 없게 되었고, 비밀번호 재설정 직전에 이메일 인증 여부까지 확인하게 되면서 공격이 차단되었다.
Step 2. 패치 적용이 어려운 경우
보안 패치를 즉시 적용하기 어려운 경우에는 비밀번호 찾기 기능을 비활성화하여 공격 경로를 차단하는 방법도 있다. 이 취약점은 인증 없는 공격자가 비밀번호 찾기로 재설정 흐름에 진입 가능해야 성립하므로, 해당 realm 에서 이 기능을 끄면 진입점 자체가 막혀 공격을 임시로 차단하는 효과가 있다. Keycloak은 realm 단위로 이 기능을 제어하며, 관리자 콘솔이나 CLI를 통해 비활성화하면 된다.
| 방법 | 적용 절차 |
|---|---|
| 관리자 콘솔 | realm settings → Login 탭 → Forgot password Off |
| kcadm CLI | kcadm.sh update realms/<realm-name> -s resetPasswordAllowed=false |
다만 이는 정상적인 비밀번호 재설정 편의를 포기하는 임시 조치일 뿐 근본적인 해결책은 아니다. 따라서 완화책은 패치 적용 전까지만 사용하고, 최종적으로는 보안 패치를 적용해야 한다.
■ 보안 패치의 한계
이번 패치는 상태값을 실행 단계 단위로 구분하고, 메일 발송 단계에서 계정 소유를 확인하도록 바꿔 이 우회를 차단했다. 다만 요청에서 authenticationExecution 필드를 빼면 서버가 상태값 정리를 건너뛰는 불완전한 처리와, tryAnotherWay가 사용자 폼이 없는 단계에도 폼을 만들어 action()을 실행시킬 수 있는 위험은 여전히 남아 있다. 이러한 요소가 다른 단계나 인증자와 맞물리면 새로운 우회로 이어질 수 있어 주의가 필요하다.
■ 참고 사이트
• Red Hat Security
◦ https://access.redhat.com/security/cve/CVE-2026-18963
• GitHub Security Advisory
◦ https://github.com/advisories/GHSA-4gv3-mc9p-5wqc
• Keycloak
◦ https://github.com/keycloak/keycloak/releases
• Keycloak Docs
◦ https://www.keycloak.org/docs/latest/server_admin/index.html
realm: Keycloak에서 사용자와 인증 설정을 독립적으로 관리하는 영역