Windows Server 2022 の AD FS に更新プログラムを適用した直後から、OpenID Connect(OIDC)の認証フローが突然こける——そんな現場の悲鳴が相次いでいます。原因は KB5065432 適用後、/adfs/oauth2/authorize からクライアントへリダイレクトされる際に state が二重に URL エンコードされてしまう不具合。この記事では、素早く復旧する実践策から恒久対策、検証・監視のポイントまで、運用目線で徹底解説します。
KB5065432 適用後に発生する「state 二重エンコード」不具合の全体像
Windows Server 2022 の AD FS 環境に KB5065432 を適用すると、OIDC の認可エンドポイント /adfs/oauth2/authorize からクライアントの redirect_uri へ 302 リダイレクトされるタイミングで、クエリパラメーター state が二重に URL エンコードされる場合があります。典型的には %3D(= のエンコード)が %253D に、% は %25 に変換され、アプリ側で保持している元の state と一致しなくなります。その結果、CSRF 防御やリプレイ対策に使われる state の検証が失敗し、コールバックで 400 エラーや「state mismatch」などの例外が発生します。
なお、最初の報告ではタイトルに KB5064532 と誤記されていましたが、実際に問題を引き起こしているのはKB5065432です。本記事では実害が確認された KB5065432 を対象に解説します。
想定読者
- AD FS(Windows Server 2022)を IdP として運用している管理者
- 社内外の OIDC 連携(SaaS・自社アプリ)を担当するエンジニア
- アップデート適用後に OIDC 認証障害が発生し、早期復旧と再発防止を求められている方
発生パターンと症状
- OIDC の認可コードフロー、ハイブリッドフロー等で発生
Locationヘッダーのstateに%25が含まれるなど、二重エンコードの痕跡が見える- 現象は断続的(intermittent)に起こる報告があり、負荷や並列リクエストのタイミングで顕在化しやすい
- サードパーティ製アプリではアプリ側回避ができず、業務停止に直結
素早い状況把握:二重エンコードの診断方法
二重エンコードの有無は、ブラウザーのデベロッパーツールや HTTP キャプチャ(Fiddler、curl、PowerShell)で 302 Found の Location ヘッダーを確認するのが最短です。リダイレクトを追わずに一次レスポンスだけを取得してください。
確認の視点
- 痕跡の文字列:
%25(=%のエンコード)がstateに含まれる - 比較の作法:受け取った
stateを 1 回だけ URL デコードして、アプリ側に保存したオリジナル値と一致するか確認 - 再現性:連続で試行し、再現が断続的か、特定条件(負荷・LB・プロキシ経由など)で生じるかを切り分け
PowerShell で 302 レスポンスの Location を確認
$uri = "https://adfs.example.com/adfs/oauth2/authorize?client_id=...&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb&scope=openid&state=abc%3D%3D"
try {
$resp = Invoke-WebRequest -Uri $uri -MaximumRedirection 0 -ErrorAction Stop
} catch {
$resp = $_.Exception.Response
}
$resp.StatusCode
$resp.Headers["Location"]
curl でヘッダーのみ表示
curl -s -I -L --max-redirs 0 \
"https://adfs.example.com/adfs/oauth2/authorize?client_id=...&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcb&scope=openid&state=abc%3D%3D"
# Location: https://app.example.com/cb?code=...&state=abc%253D%253D (二重エンコードの例)
二重エンコードの判定ロジック(安全な例)
「常に 2 回デコード」するのは危険です。次のように「オリジナル → デコード → 再エンコード」を比較する安全策を取りましょう。
// C#(.NET)
string original = receivedState; // コールバックで受け取った state
string onceDecoded = Uri.UnescapeDataString(original);
string reEncoded = Uri.EscapeDataString(onceDecoded);
bool looksDoubleEncoded = string.Equals(reEncoded, original, StringComparison.Ordinal);
// 必要なときだけ 1 回だけデコードして使う
string effective = looksDoubleEncoded ? onceDecoded : original;
// Node.js(ブラウザ/サーバ)例
const original = receivedState;
const onceDecoded = decodeURIComponent(original);
const reEncoded = encodeURIComponent(onceDecoded);
const looksDoubleEncoded = reEncoded === original;
const effective = looksDoubleEncoded ? onceDecoded : original;
// Java
String original = receivedState;
String onceDecoded = java.net.URLDecoder.decode(original, java.nio.charset.StandardCharsets.UTF_8);
String reEncoded = java.net.URLEncoder.encode(onceDecoded, java.nio.charset.StandardCharsets.UTF_8);
boolean looksDoubleEncoded = reEncoded.equals(original);
String effective = looksDoubleEncoded ? onceDecoded : original;
# Python
from urllib.parse import quote, unquote
original = received_state
once_decoded = unquote(original)
re_encoded = quote(once_decoded, safe="")
looks_double = (re_encoded == original)
effective = once_decoded if looks_double else original
即効性のある対処とリスク整理
最短で復旧するなら KB5065432 のアンインストール(ロールバック)が確実です。ただしセキュリティ修正も同時に戻ってしまうため、恒久対策(公式修正)を待つ間のリスク管理が重要になります。以下に選択肢を整理します。
| 対応策 | 内容 | 長所 | 短所・注意点 |
|---|---|---|---|
| KB5065432 をアンインストール | 問題発生前の状態に戻す | 即効・確実 | セキュリティ修正も巻き戻る。再配信のブロックが必要 |
| アプリ側で一次デコードを挟む | 受信した state を必要時のみ 1 回 URLDecode() | 自社アプリなら暫定運用が可能 | 二重か否かの判定実装が必要。サードパーティでは不可 |
state を Base64Url 化して送出 | % や = を含まない値にして影響を回避 | 外部公開値のフォーマット調整だけで効果 | 送受信の実装変更が必要。全フローで通用しない場合あり |
| 公式修正を待つ | Microsoft 側で原因調査中とされる事象 | 恒久的で安全な解決が期待できる | 公開時期が未定。公開まで障害が続く |
アンインストール(ロールバック)の具体手順とブロック
影響を即座に止めるなら、まずは AD FS 全ノードから KB5065432 を削除します。フェデレーション サービスのダウンタイムを最小化するため、WAP(AD FS Proxy)を含む全台でメンテナンス順序を設計してください。
WUSA でのアンインストール
wusa /uninstall /kb:5065432 /quiet /norestart
# その後、計画停止のタイミングで再起動
DISM(パッケージ名が必要な場合)
DISM /Online /Get-Packages | Select-String "KB5065432"
# 出力された Package Identity を使って削除
DISM /Online /Remove-Package /PackageName:<Package_Identity> /Quiet /NoRestart
再配信のブロック(WSUS / グループポリシー)
- WSUS を利用している場合:該当 KB を 未承認/拒否 に設定し、AD FS を含むサーバー グループへ配布されないようにする
- グループポリシー:自動更新を一時停止(品質更新の延期や「ダウンロードは自動、インストールは手動」など)
- Windows Update for Business(WUfB):品質更新を指定日数延期し、検証リングを分ける
ロールバックはセキュリティレベルを下げる判断でもあります。境界防御(WAF・VPN・ゼロトラスト側)で補完策を取り、運用監視を強化してください。
アプリ側での暫定回避(一次デコード)
自社でコード修正できる場合、コールバック受信時に「二重エンコードの疑いがあるときだけ 1 回だけデコード」する暫定策が有効です。以下に代表的なフレームワークの差し込みポイント例を示します。
| スタック | 差し込みポイントの例 | メモ |
|---|---|---|
| ASP.NET Core | OIDC ミドルウェアの Events.OnMessageReceived / OnTokenValidated | クエリの state を検査し、必要時のみ一次デコード |
| Java(Spring Security) | OAuth2AuthorizationRequestResolver / カスタムフィルタ | state を検証前に整形 |
| Node.js(openid-client) | コールバック処理の前段(Express ミドルウェア) | クエリの正規化関数を共通化 |
| Python(Authlib 等) | Flask/Django のビュー直前で正規化 | 再エンコード比較ロジックで過剰デコードを回避 |
注意点として、無条件に二重デコードすると、正常系(単一エンコード)で不整合を生むリスクがあります。必ず「再エンコード比較」や「%25 の存在確認」などの判定を入れてください。
state を Base64Url 化して送出する回避
state の原材料(JSON 文字列やランダムトークン)を Base64Url(+// を -/_ に置換、パディング = 省略)にしておくと、URL エンコードを経ても % や = を含まず、二重エンコードの影響を受けづらくなります。
// C# Base64Url 例
static string ToBase64Url(byte[] bytes)
{
string b64 = Convert.ToBase64String(bytes); // e.g. "abc=="
return b64.Replace("+", "-").Replace("/", "_").TrimEnd('=');
}
static byte[] FromBase64Url(string input)
{
string pad = input.Length % 4 == 2 ? "==" : input.Length % 4 == 3 ? "=" : "";
string b64 = input.Replace("-", "+").Replace("_", "/") + pad;
return Convert.FromBase64String(b64);
}
// JS Base64Url 例(ブラウザ/Node)
function toBase64Url(buf) {
const b64 = btoa(String.fromCharCode(...buf));
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
function fromBase64Url(s) {
const pad = s.length % 4 === 2 ? "==" : s.length % 4 === 3 ? "=" : "";
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
}
この方法は「送る側での実装変更」で済みますが、外部サービスやサードパーティ製品では適用できないケースがあります。自社アプリに限定した暫定策として検討してください。
恒久対策に向けた運用計画
最終的な解決は、AD FS 側に提供される公式修正プログラムの適用です。公開までの間は、以下の運用ガイドに従って影響を最小化しましょう。
パッチリング設計(リング方式)
- 検証リング:検証用 AD FS/WAP を用意し、品質更新を先行適用。OIDC 代表ワークロードで自動テスト
- パイロットリング:限定したユーザー/クライアントで段階適用
- 本番リング:業務の閑散時間帯に切り替え、ロールバック手順を事前に演習
自動テスト(回帰テスト)の観点
| 観点 | チェック内容 | OK 条件 |
|---|---|---|
| 一次レスポンス | /authorize 302 の Location に %25 が混入しない | state を 1 回デコードした値が送信値と完全一致 |
| CSRF 防御 | セッションに保存した state とコールバック受信値が一致 | 認証は成功し、セッション固定化の兆候なし |
| 並列リクエスト | 同一ブラウザーで短時間に複数フローを発火 | 断続的な失敗が再現しない |
| プロキシ/負荷分散 | WAP/リバプロ/LB を経由しても挙動が同一 | スティッキーセッションの有無で差異なし |
監視とアラート
- アプリ側:
state mismatch、invalid_state、400/302 異常比率をメトリクス化 - AD FS:AD FS 管理ログ、セキュリティログ のエラー増加。トークン発行/認証要求の成功率を可視化
- ネットワーク:WAP/プロキシ/LB の 4xx レート。異常時にスナップショット採取
設計・構成の落とし穴と対処ヒント
プロキシ/リバースプロキシのエンコード干渉
アプリケーション リクエスト ルーティング(ARR)や Nginx、各種 WAF で URL 正規化オプションを有効にしていると、% を再エンコードしたり、二重デコードを防ぐために逆に再エンコードする挙動を取る場合があります。問題の切り分けでは、まず AD FS へ直接アクセスした場合と WAP/プロキシ経由を比較し、差異がなければ OS 更新(KB5065432)起因の可能性が高いと判断できます。
OIDC ミドルウェアの保存値と照合値の差
多くのフレームワークは state をセッションやクッキーに保存します。保存時はエンコード前の生値、照合時はコールバックで受け取った値を比較するため、途中で二重エンコードが入ると整合しません。暫定対処を入れる場合は「照合直前の正規化」だけに限定し、保存側の仕様は変えないのが安全です。
セキュリティ観点の注意
stateは CSRF やリプレイ対策に関わるため、暫定回避のロジックは最小限にし、デコード回数を無制限にしないcodeやid_tokenなど他パラメーターには触れない(別の検証に影響)- ログに
stateを出す場合はマスキング(漏えい対策)
現場で役立つ「すぐ使える」スニペット集
PowerShell:二重エンコード検出ユーティリティ
function Test-DoubleEncodedState {
param([string]$State)
$once = [System.Uri]::UnescapeDataString($State)
$re = [System.Uri]::EscapeDataString($once)
return $re -eq $State
}
# 使い方
$state = "abc%253D%253D"
if (Test-DoubleEncodedState $state) {
Write-Host "二重エンコードの疑いあり:$state"
}
IIS(WAP)運用メモ
- AD FS/WAP の再起動やアプリプールのリサイクルで改善しない場合は、やみくもな設定変更よりもまずロールバックで影響遮断を優先
- 監査の都合で更新を残したい場合は、影響度の低い連携から段階的に暫定回避(一次デコード・Base64Url)を導入し、影響範囲を縮小
検証シナリオ例(社内確認用)
以下は社内での再現・検証・リグレッションのひな形です。テスト自動化の材料としても利用できます。
前提
- Windows Server 2022 + AD FS(フェデレーションサーバー)
- WAP(任意)
- テスト用 OIDC クライアント(
redirect_uriは HTTP エコーで十分)
シナリオ
- シナリオ A:KB5065432 を適用したノードで
/authorizeを実行し、Locationのstateに%25が混入するか確認 - シナリオ B:負荷(並列 10〜50)で繰り返し、断続的に失敗が出るか確認
- シナリオ C:ロールバック後、同条件で再試験し、二重エンコードが消えることを確認
- シナリオ D:アプリ側暫定回避(一次デコード)を入れた場合、正常・異常の双方で整合が取れるか確認
よくある質問(FAQ)
Q:SAML 連携も影響しますか?
A:本件は OIDC(/oauth2/authorize)で報告されている事象です。SAML(/adfs/ls/)は連携仕様が異なるため、同一の state 二重エンコードによる障害は通常は発生しません。ただし同時期の更新で別の影響がないかは個別確認が必要です。
Q:現象が「たまに」しか起きません。なぜですか?
A:断続的に再現する報告があり、並列数やタイミング、ノード/プロキシの経路差など条件依存の可能性があります。負荷試験で再現性を高め、キャプチャを採るのが有効です。
Q:暫定回避の一次デコードは安全ですか?
A:適切な判定(再エンコード比較)を入れて1 回だけデコードする設計であれば、state の整合性維持に寄与します。無条件の二重デコードや他パラメーターへの適用は避けてください。
Q:Base64Url の導入メリットは?
A:% や = を含まない値にできるため、URL エンコードの影響を受けづらく、今回の不具合でも影響を回避しやすくなります。送受信双方の実装修正が必要です。
復旧後にやるべきこと(チェックリスト)
- AD FS/WAP の全ノードで KB5065432 の状態を確認(混在を避ける)
- WSUS/ポリシーで再配信をブロックし、更新リングを整理
- 影響を受けた連携先に障害報告と復旧連絡(再試験の依頼を含む)
- アプリ側に暫定対策を入れた場合:正式修正公開後に速やかに撤去
- 監視ダッシュボードに
state mismatch、302→400 の比率、Locationの健全性チェックを追加
トラブルシュートの実戦テクニック
「受け側」で正しく比較できる状態を作る
失敗時の実際の state をログする際は、受信値(エンコード済み)、一次デコード値、保存済みのオリジナル値の 3 点セットで記録すると、後から原因を特定しやすくなります(ただし機密管理に注意)。
「%25」を指標にした検知
# NGINX 等のログから state を抽出して「%25」を粗く検知
grep "state=" access.log | grep "%25" | wc -l
正規化関数を共有化してヒューマンエラーを防ぐ
複数のサービスが同じ AD FS を共有していると、各サービスで暫定回避の実装がバラバラになりがちです。「再エンコード比較→必要なら 1 回だけデコード」という正規化関数を共通ライブラリ化すると、回収も一括で行いやすくなります。
意思決定の指針:どの選択肢を取るべきか
| 状況 | 推奨アクション | 理由 |
|---|---|---|
| サードパーティ製アプリが停止している | アンインストール一択(再配信ブロック) | 受け側改修ができず、業務影響が継続するため |
| 自社アプリのみ影響 | 暫定回避(一次デコード or Base64Url)+ロールバックは回避 | セキュリティ修正を保持したまま運用継続が可能 |
| 広範な SaaS 連携が障害 | ロールバック+WSUS ブロック+関係者へ周知 | 自社側では対処不能のため、まず復旧を優先 |
| 検証環境でしか再現しない | リング方式で様子見しつつ監視強化 | 断続的なため、本番適用前に観測を続ける |
障害の背景にある仕組み(理解すると対応が速い)
OIDC の state は「クライアントが生成し、IdP を経由して戻ってくる」相関値です。クライアント→IdP の送信時には URL エンコードが 1 回施され、IdP は通常これを透過的にリダイレクト先へ埋め込みます。中継点(IdP/プロキシ)のどこかで値の二重エンコードが入ると、戻り値はオリジナルと一致しません。今回の不具合はまさにこの「透過の約束」が破られることで起こる典型的な障害です。
この特性上、state のフォーマットを「URL エンコードの影響を受けにくい」形(Base64Url)にしておく設計は、将来のリスク低減にも有効です。ただし、すでに稼働中の連携では互換性と移行計画を必ず検討してください。
まとめ:いま取るべき現実解
- 最短の復旧手段は KB5065432 のアンインストール(再配信ブロックを忘れずに)
- 自社コードを触れるなら、「必要時のみ 1 回だけデコード」の暫定回避、または Base64Url 化で影響を局所化
- サードパーティ製アプリが止まる場合はロールバック一択。関係者への周知と再試験をセットで
- 公式修正が出たら速やかに適用し、暫定コードや更新ブロックを元に戻す
- 今後に備え、更新リング、監視、回帰テストを整備し、同系障害の再発を未然に防ぐ
付録:現場で使える小ネタ集
障害チケット/報告テンプレート(要約)
- 事象:KB5065432 適用後、OIDC の
stateが二重 URL エンコードされ、クライアント側検証に失敗 - 影響:対象連携(アプリ名とユーザー数)で認証不可/断続的失敗
- 暫定対応:<ロールバック / 一次デコード / Base64Url>
- 恒久対策:公式修正適用予定。公開時期は未定、監視で追従
- 再発防止:リング方式、回帰テスト、ダッシュボード整備
正規表現での粗い検知
state=([^&]+) # 取り出し
%25 # 二重エンコードの痕跡(粗検知)
「一次デコード」導入後のセルフテスト
- 二重/単一の双方で成功するか(自動テスト)
- ログにオリジナル・一次デコード・比較結果を出す(短期間のみ)
- セキュリティレビュー(state の扱い、ログ保護、例外メッセージ)
最終確認:意思決定のチェックポイント
- ビジネス影響は「停止」か「遅延」か(SLA)
- ロールバックでの新たな脆弱性リスクを許容できるか
- 暫定回避が適用できる範囲(自社/委託/サードパーティ)
- 公式修正公開後の巻き戻し計画(デッドコードの撤去、設定の解除)
- 監視と検証の自動化レベル(人的依存の排除)
障害の第一報から復旧、そして再発防止までの一連の動きは、どの組織でも大差ありません。大切なのは、「即時復旧」と「セキュリティ」と「可観測性」を同時に満たす現実的な一歩を、確実に積み上げることです。本記事の手順と観点をそのまま転用し、あなたの環境に最適化してください。

コメント