JWT authentication bypass via jwk header injection
概要
JWTヘッダーの jwk パラメータに攻撃者が用意した公開鍵を埋め込み、サーバー側にその鍵で署名を検証させることで、任意のユーザー(管理者など)になりすます攻撃手法。
調査・前提条件の確認
- JWTを使用しているか: リクエストヘッダーやCookieにJWTが含まれているか確認。
-
署名アルゴリズム:
RS256などの非対称暗号(公開鍵暗号)が使用されているか。 -
既存の脆弱性チェック:
-
subをadministratorに変更するだけで通るか(署名検証不備)。 -
algをnoneに変更して通るか。 - 既知の秘密鍵や弱い秘密鍵が使われていないか。
-
-
JWKヘッダーの受け入れ: サーバーがヘッダー内の
jwkパラメータを処理し、信頼してしまう実装になっているか。
攻撃手順
-
新しいRSA鍵ペアの生成:
- Burp Suiteの「JWT Editor Keys」タブなどで、攻撃者用の新しいRSA鍵ペア(秘密鍵と公開鍵)を生成する。
-
JWTの改ざん:
- ペイロードの
subクレームなどをadministratorに変更する。
- ペイロードの
-
JWKヘッダーインジェクション:
- JWTヘッダーに
jwkパラメータを追加し、手順1で生成した公開鍵の情報を埋め込む。 - 同時に、ヘッダーの
kid(Key ID) を削除または更新する(サーバーの実装による)。
- JWTヘッダーに
-
署名の再生成:
- 手順1で生成した秘密鍵を使用して、改ざんしたJWTに署名を行う。
-
リクエスト送信:
- 改ざん・再署名したJWTを送信し、管理者権限が取得できたか確認する。
対策
-
信頼できる鍵のみを使用する: ヘッダー内の
jwkパラメータを無条件に信頼せず、サーバー側でホワイトリスト化された信頼できる公開鍵(またはJWK Set URL)のみを使用して検証を行う。 -
jwkヘッダーの無効化: そもそもクライアントから送られてくる公開鍵を使用する設計にしない。kidを使用してサーバー内部の鍵ストアから鍵を参照するようにする。
クリアするまでにかかった時間
20m
所感
- 「秘密鍵の推測」とは異なり、「検証に使う鍵そのものを攻撃者が指定させる」という点がユニークだった。
- サーバーが「渡された鍵で検証に成功したからOK」という単純なロジックになっていると、この脆弱性が生まれることを理解した。
メモ:理解を深めるための補足
JWK (JSON Web Key) とは
- JSON形式で暗号鍵(公開鍵など)を表現するための標準規格(RFC 7517)。
- 構成要素の例:
-
kty(Key Type): 鍵の種類(例: RSA)。 -
n(Modulus): RSA公開鍵の法。 -
e(Exponent): RSA公開鍵の指数。 -
kid(Key ID): 鍵を識別するID。
-
jwk header injection の仕組み
通常、JWTの署名検証フローは以下の通りです。
- サーバーは自分の秘密鍵でJWTを作成・署名してクライアントに渡す。
- クライアントからJWTが戻ってきた際、サーバーは手元の公開鍵(または秘密鍵)で署名を検証する。
脆弱性がある場合のフロー:
- 攻撃者は自分のPCで勝手に鍵ペア(秘密鍵A・公開鍵A)を作る。
- JWTのヘッダーに「このトークンは公開鍵Aで検証してね」という情報(
jwkパラメータ)を埋め込む。 - 攻撃者は秘密鍵AでJWTに署名する。
- サーバーはヘッダーを見て、「なるほど、公開鍵Aを使えばいいのか」と判断し、検証を行う。
- 当然、秘密鍵Aと公開鍵Aはペアなので検証は成功する。
- サーバーは「署名が正しい=正当な発行者が作ったトークンだ」と誤認し、改ざんされた内容(管理者権限など)を受け入れてしまう。
類似攻撃との違い
- alg=none: 署名検証そのものをスキップさせる。
-
JKU (JWK Set URL) Injection:
jkuヘッダーを使い、外部サーバーにあるjwks.jsonを参照させる(外部通信が必要)。 - JWK Injection: JWTの中に直接公開鍵データを埋め込む(外部通信不要)。