現場のエンジニア諸君、お疲れ様。セキュリティチームのチーフだ。
今日は「JWT(JSON Web Token)」という、現代のWeb認証の心臓部を狙ったエグい攻撃の話をしよう。多くの開発者が「ライブラリを使っているから大丈夫」と高を括っているが、そのライブラリの設定ひとつで、扉は全開になり得る。
特に alg: none と「鍵混同(Key Confusion)」は、認証バイパスの古典にして頂点だ。なぜこれが今も防げないのか。それは、多くのエンジニアが「JWTの仕様」ではなく「ライブラリの便利機能」を信じすぎているからだ。
—
1. なぜ「none」で認証が突破されるのか?
JWTはヘッダー(アルゴリズム指定)、ペイロード(データ)、署名の3段構成だ。攻撃者はヘッダーを {"alg": "none"} に書き換え、署名部分を削除(あるいは空に)する。
古い、あるいは設定が甘いJWTライブラリは、この none を「署名検証不要」と解釈し、ヘッダーに書かれた通りに検証をスキップしてしまう。結果、攻撃者はペイロードの user_id を admin に書き換えるだけで、ログイン処理を素通りできるわけだ。
PoC:脆弱な実装のイメージ
攻撃者は以下のようなJSONをBase64Urlエンコードして送る。
// 改ざんされたヘッダー
{“alg”: “none”, “typ”: “JWT”}
// 改ざんされたペイロード
{“user_id”: “admin”, “iat”: 1600000000}
// 署名は空っぽ(末尾にドットだけ残す)
サーバー側で「署名を検証しない」ロジックが走れば、ゲームオーバーだ。
—
2. 鍵混同(Key Confusion Attack)の恐怖
これはさらに巧妙だ。非対称鍵(RS256など)を使用するシステムに対して、攻撃者がわざとアルゴリズムを対称鍵(HS256)に変更してリクエストを送る手法だ。
1. サーバーは公開鍵(Public Key)を「HS256の秘密鍵」として読み込んでしまう。
2. 攻撃者は、サーバーの公開鍵を「秘密鍵」として使い、HMACで署名を生成する。
3. サーバー側は「公開鍵」を使ってHMACの検証を行い、整合性が取れてしまうため「正当な署名だ」と判断する。
RSAの公開鍵はWeb上に公開されていることが多いため、これは理論上、非常に高確率で成立する。
—
3. 実践:セキュアな実装コード(Node.js / jsonwebtoken)
ライブラリを信じすぎるな。「検証時にアルゴリズムを強制する」ことが唯一の解だ。
const jwt = require(‘jsonwebtoken’);
// 悪い例:受け取ったJWTのヘッダーにある alg をそのまま信用する
// const decoded = jwt.verify(token, publicKey);
// 良い例:期待するアルゴリズムを明示的に指定する
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: [‘RS256’] // ここでRS256以外を弾く!noneもHS256も全て拒否
});
console.log(“検証成功:”, decoded);
} catch (err) {
console.error(“署名検証失敗。攻撃の可能性あり:”, err.message);
}
Python (PyJWT) の場合
Pythonでも同様だ。algorithms 引数を省略してはいけない。
import jwt
期待するアルゴリズムをリストで渡す
try:
payload = jwt.decode(token, public_key, algorithms=[“RS256”])
except jwt.InvalidAlgorithmError:
# アルゴリズムが期待と異なれば例外を投げさせる
log_security_event(“不正なアルゴリズムが指定されました”)
—
4. インフラ・設計レベルでの防御戦略
コードだけでなく、インフラと設計思想でも守りを固める。
1. アルゴリズムのハードコード
認証トークンを発行・検証するマイクロサービスでは、使用するアルゴリズムを環境変数で固定し、ライブラリのデフォルト設定を上書きするラッパー関数を必ず作れ。
2. 公開鍵の管理
公開鍵を直接コードに書かず、JWKS (JSON Web Key Set) エンドポイントを利用しろ。その際、ライブラリがJWKSのメタデータを正しく検証しているか、最新バージョンを使用しているかを常にチェックすること。
3. WAFでのフィルタリング(Nginxの例)
WAF(AWS WAF等)でJWTを検査するのは難しいが、リクエストヘッダーの Authorization を見て、明らかに異常なJWT形式や、none という文字列が含まれるパケットを異常値としてロギングする設定は検討の価値がある。
NginxでAuthorizationヘッダーを検査する簡易的な例
if ($http_authorization ~ “none”) {
return 403; # ヘッダーにnoneが含まれていたら即座に遮断
}
—
チーフからのアドバイス
「動くコード」を書くのはジュニアエンジニアの仕事だ。「攻撃者がどうやって破壊するかを想像しながらコードを書く」のが、シニアエンジニアの仕事だ。
JWTは便利だが、実装の複雑さを隠蔽する「魔術」になりやすい。ライブラリがよしなにやってくれることを期待せず、検証プロセスには必ず「意図しないアルゴリズムを拒否するホワイトリスト」を組み込め。
今日から君たちのプロジェクトのJWT検証コードを見直してくれ。もし algorithms 引数が空っぽなら、それが君たちのシステムの最大の脆弱性だ。
現場からは以上だ。何かあればまた相談してくれ。
コメント