
Keycloak CVE-2026-18963 のエクスプロイト。reset-credentials バイパスによる未認証アカウント乗っ取りを可能にします。安全な検出、非破壊的な証明、完全な乗っ取り、ユーザー名列挙、および脆弱なバージョンと修正済みバージョンを備えたラボが含まれます。
ユーザー名またはメールアドレスだけを知っていれば、未認証の攻撃者は任意の Keycloak アカウントに 任意のパスワードを設定できます。パスワードリセットメールは実際の被害者に配信されますが、 決して必要とされることはありません。攻撃者はメールボックスを読むことも、リンクをクリックすることもなく、 以前の認証情報やセッションも保持していません。
影響を受けるバージョン: Keycloak 26.0.0 – 26.7.1。26.7.2 で修正済み。
コマンド1つ。有効なユーザー名は不要で、副作用もありません — メールは送信されず、 アカウントへの書き込みも行われず、悪用可能なステップの手前で停止します。```bash git clone https://github.com/Snizi/CVE-2026-18963-Exploit cd CVE-2026-18963-Exploit
python3 cve_2026_18963_poc.py
--base https://sso.example.com --realm YOUR_REALM
--client-id account
--redirect-uri https://sso.example.com/realms/YOUR_REALM/account/
--safe-check
Python 3.9+、標準ライブラリのみ。インストール不要。
| Exit | 判定 | 意味 |
|:---:|---|---|
| `0` | 🔴 **脆弱** | 駐車されたメールゲートが配信された — バグそのもの |
| `2` | 🟢 **修正済み** | フローはログインに分岐し、そのまま留まった(修正 #51844 が存在) |
| `2` | 🟡 **緩和済み** | 資格情報リセットに到達不能 — *パスワードを忘れた場合* はオフ。**パッチではありません。** |
| `3` | ⚪ **判定不能** | 認識できない応答 — **合格と見なさないでください** |
各レルムに対して実行してください — *パスワードを忘れた場合* はレルムごとの設定であり、`master` も対象です。
詳細と、このチェックがユーザーを必要とせず何も変更しない理由は、
[§4a](#4a-safe-detection---safe-check--start-here) にあります。
**すでに影響を受けていることが分かっていますか?** [修復](#8-remediation) と
[検出 / 脅威ハンティング](#9-detection) に進んでください。
### ターゲットなしで試す
このリポジトリには、同一のレルムに対して脆弱な **26.7.1** と修正済みの **26.7.2** を並べて起動するラボが含まれており、さらに、アカウントが乗っ取られている間もリセットメールが届いて未読のままになるのを確認するメールボックスも含まれています:```bash
cd lab && docker compose up -d
python3 ../cve_2026_18963_poc.py --base http://localhost:8080 \
--realm poc --client-id poc-app --safe-check # VULNERABLE
python3 ../cve_2026_18963_poc.py --base http://localhost:8100 \
--realm poc --client-id poc-app --safe-check # PATCHED
⚠️ 認可されたテストのみ
このリポジトリは、防御側の担当者、インシデントレスポンダー、および認可を受けた ペネトレーションテスターのために存在します。所有しているシステム、または書面による テスト許可を得たシステムに対してのみ実行してください。ここにあるすべてのものには、 自己完結型の脆弱性ラボ(
lab/)が同梱されているため、このバグの仕組みを学ぶために 外部のリソースに触れる必要はありません。許可なく第三者のインフラストラクチャに 向けることは、ほとんどの法域で違法であり、このプロジェクトはそれを支援しません。
参照: keycloak#51833 · GHSA-4gv3-mc9p-5wqc · 修正 keycloak#51844
2つの欠陥が連鎖します。どちらか単独では悪用できません。
services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
processAction() — フォームキー tryAnotherWay を含む任意のPOST:```java
processor.getAuthenticationSession().setAuthNote(
AuthenticationProcessor.AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, "true");
return createSelectAuthenticatorsScreen(model);
このメモは、**どの実行セットによって設定されたかの記録を持たない単なるブール値**である。これは、
送信された `authenticationExecution` パラメータを処理する分岐でのみクリアされる。
そのパラメータを省略すると — この PoC が全体を通して行うように — フラグは認証セッションの
存続期間中セットされたままになる。
`processFlow()` — フラグが truthy である間、通常のフロー評価はスキップされる:```java
if (Boolean.parseBoolean(authSession.getAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED))) {
String lastExecutionId = authSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
if (lastExecutionId != null) {
AuthenticationExecutionModel executionModel =
realm.getAuthenticationExecutionById(lastExecutionId);
if (executionModel != null)
return createSelectAuthenticatorsScreen(executionModel); // <-- attacker-usable form
}
}
これは、現在待機中の任意の実行を対象とした送信可能なフォームをレンダリングし、 セッションを "メールの待機" に固定するのではなく。
接着剤は processResult() の case FORK: です — Send Reset Email が発火すると、
CURRENT_AUTHENTICATION_EXECUTION = <reset-credential-email execution id> を記録し、
ブラウザをログインページにフォークします。待機中の実行こそがまさにメールゲートです。
`services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java````java @Override public void action(AuthenticationFlowContext context) { context.getUser().setEmailVerified(true); context.success(); }
無条件。フローが有効なアクショントークンによって再開されたことを検証するものは何もないため、
`action()` に *到達すること* が、メールボックス制御を証明することと同等と見なされます。
### チェーン```
tryAnotherWay POST → sticky AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED="true"
submit victim identifier → mail sent to victim, e-mail execution parked (FORK)
re-enter reset-credentials → sticky flag serves a form targeting the parked e-mail execution
POST that form → ResetCredentialEmail.action() → success() → gate bypassed
→ flow advances to UPDATE_PASSWORD → attacker sets the password
6つのHTTPリクエスト、認証なし、どの時点でもauthenticationExecutionパラメータ
はありません。
model.getId() を保存し、processFlow() はそれが
CURRENT_AUTHENTICATION_EXECUTION と等しい場合のみ尊重し、それ以外の場合は削除します。攻撃では、これら2つは異なります(choose-user IDとe-mail-gate ID)—
これはまさにパッチが検出するものであり、まさに
--safe-check が頼りにするシグナルです。ResetCredentialEmail.action() は現在
context.getUser().getId().equals(authNote(ACTION_TOKEN_USER_ID))
を必須とし、それ以外の場合は INVALID_USER で失敗します。| 系列 | 影響を受けるバージョン | コミュニティ修正 |
|---|---|---|
| レガシー(WildFlyベースのKeycloak、≤ 17) | 影響なし | — |
| Quarkus 17 – 25.x | 影響なし | — |
| 26.0 | 26.0.0 – 26.0.17 | なし |
| 26.1 | 26.1.0 – 26.1.5 | なし |
| 26.2 | 26.2.0 – 26.2.16 | なし |
| 26.3 | 26.3.0 – 26.3.5 | なし |
| 26.4 | 26.4.0 – 26.4.14 | 26.4.15(ベンダーバックポートタグ) |
| 26.5 | 26.5.0 – 26.5.7 | なし |
| 26.6 | 26.6.0 – 26.6.5 | 26.6.6(ベンダーバックポートタグ) |
| 26.7 | 26.7.0 – 26.7.1 | 26.7.2 |
6a9e60bb によって導入され、そのコミットは "Try another way" のauthenticator-selector画面を
リセットフローに追加しました。これより古いバージョンには単にそのコードパスがありません。これには、古い
WildFlyベースのディストリビューションとRH-SSO 7.xが含まれます。これらは この バグの影響を受けませんが、
サポート終了(EOL)のままであり、他の多くの脆弱性に対しては脆弱です。レガシービルドに留まることは
是正策ではありません。26.7.2 はコミュニティトレインで公開された唯一の修正
リリースです。デプロイメントが26.0 – 26.6にある場合、その系列には
パッチリリースはありません — 修正にはポイントリリースではなく、マイナーバージョンの
アップグレードが必要です。26.4.15 / 26.6.6 タグはベンダーバックポートであり、コミュニティイメージと
交換可能ではありません。前提条件: レルムでForgot passwordが有効になっておりかつ、バインドされているreset-credentialsフローが組み込みのreset-credential-email認証器を使用していること。
このリポジトリには、脆弱なKeycloakとパッチ適用済みのKeycloakの両方が同梱されており、同一のレルムをインポートします。さらに、リセットメールを取得するMailpitも含まれています — これにより、アカウントが乗っ取られる間、メールが届いて未読のままになるのを確認できます。```bash cd lab docker compose up -d
| サービス | URL | バージョン |
|---|---|---|
| `kc-vuln` | http://localhost:8080 | 26.7.1 — **脆弱性あり** |
| `kc-patched` | http://localhost:8100 | 26.7.2 — **パッチ適用済みコントロール** |
| `kc-mailpit` | http://localhost:8025 | 被害者のメールボックス |
レルム `poc`、パブリッククライアント `poc-app`、ユーザー `victim` / `OriginalPassw0rd!`、Keycloak
管理者 `admin` / `admin`。