JWTの「署名検証」を骨抜きにする:アルゴリズム変更攻撃の深層と鉄壁の防御策
現場でコードをレビューしていると、JWT(JSON Web Token)を「単なるセッションIDの代わり」として軽く扱っているケースに遭遇する。だが、JWTは単なる文字列ではない。設計を誤れば、攻撃者にとっての「黄金のチケット」に早変わりする代物だ。
今日は、数ある攻撃手法の中でも、認証の根幹を崩壊させる「JWTアルゴリズム変更攻撃」について、現場の知見を詰め込んで解説する。
—
1. なぜ「アルゴリズム変更攻撃」が成立するのか
JWTは、Header.Payload.Signature の3つのパートで構成される。このうち、Header には alg (アルゴリズム)が含まれている。
問題は、多くのライブラリが「どの署名検証アルゴリズムを使うか」を、トークンに含まれるこの alg ヘッダーを信頼して判断してしまう点にある。攻撃者はここを逆手に取る。
シナリオA:none アルゴリズムへの強行
サーバー側が「署名なしでもOK」という実装を許容している場合、攻撃者は alg: none に書き換えたヘッダーを送りつける。サーバーは「署名検証は不要だな」と誤認し、悪意ある Payload をそのまま受け入れてしまう。
シナリオB:HS256(対称鍵)への強制
公開鍵暗号(RS256)で署名されているトークンに対し、攻撃者はわざとアルゴリズムを HS256 に書き換える。サーバー側が「公開鍵」を「秘密鍵(共有鍵)」として解釈し、公開鍵そのものを署名の検証キーとして使ってしまうという、非常に有名な実装ミスだ。公開鍵は誰でも手に入るため、攻撃者は自分の秘密鍵で署名を偽造できる。
—
2. 実践的な防御策:コードで語る鉄壁の実装
ライブラリをデフォルトで使うのが一番危ない。「期待するアルゴリズム以外は徹底的に弾く」というホワイトリスト方式が唯一の正解だ。
Python (PyJWT) でのセキュアな実装例
PyJWT を使用する場合、必ず algorithms 引数を明示すること。これにより、ライブラリの脆弱性を突かれても、指定外のアルゴリズムを強制的に拒否できる。
import jwt
# サーバー側が想定している公開鍵(実際の運用環境からロードすること)
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n..."
def verify_token(token):
try:
# 【重要】algorithmsをリストで明示する。これ以外のアルゴリズムはエラーになる。
# 'none' や 'HS256' を含めないことが鉄則。
decoded_payload = jwt.decode(
token,
PUBLIC_KEY,
algorithms=["RS256"]
)
return decoded_payload
except jwt.InvalidAlgorithmError:
# ここにログを仕込んでおくと、攻撃の予兆を検知できる
print("警告:不正なアルゴリズムによるアクセス試行")
return None
except Exception as e:
return None
JavaScript (Node.js/jsonwebtoken) での実装例
Node.js環境でも同様だ。verify 関数のオプションを厳格に設定する。
const jwt = require('jsonwebtoken');
const verifyOptions = {
// 【重要】許容するアルゴリズムを厳密に制限
algorithms: ['RS256'],
issuer: 'my-auth-server' // 発行者の検証もセットで行うのが定石
};
function verifyToken(token, publicKey) {
try {
return jwt.verify(token, publicKey, verifyOptions);
} catch (err) {
// 検証失敗時はログを残してセッションを破棄する
console.error('JWT検証失敗:', err.message);
return null;
}
}
—
3. インフラ・アーキテクチャレベルでの防壁
アプリケーションコードでの対策は必須だが、多層防御としてゲートウェイでの制御も有効だ。
Nginx でのヘッダーフィルタリング
もしJWTをヘッダーでやり取りしているなら、alg ヘッダーが特定の不審な値でないか、Nginx側で簡易的にチェックするのも手だ(※完全な検証はアプリで行うこと)。
# Nginxの設定例: 特定の怪しいヘッダーパターンをブロック
if ($http_authorization ~* "alg%22%3A%22none") {
return 403;
}
クラウドIAM・WAFの活用
WAF(AWS WAF等)を使用している場合、JWTの構造を解析するルールセットを適用することも可能だ。特に alg: none が含まれるリクエストをブロックするルールは、攻撃の入り口を塞ぐ高い効果がある。
—
4. 最後に:エンジニアへのアドバイス
JWTは便利だが、その柔軟性が脆弱性に直結している。
1. ライブラリを最新に保つ: PyJWT や jsonwebtoken は、過去に何度も「アルゴリズムの検証不備」でCVEが出ている。依存関係は定期的に更新しよう。
2. ログを信頼せよ: InvalidAlgorithmError や InvalidSignatureError が発生した際は、単にエラーを返すだけでなく、「誰が・いつ・どのIPから」攻撃しようとしたのかをログに記録し、SIEMや監視ツールでアラートを鳴らせ。
3. 「デフォルト」を疑え: セキュリティ設定において「デフォルトで動く」ことは「攻撃に対して無防備」と同義だ。
コードは嘘をつかない。君が書いたその一行が、明日誰かの個人情報を守るかもしれないし、逆にサーバーの鍵を奪うきっかけになるかもしれない。常に「最悪の攻撃者」の視点を忘れないでほしい。
健闘を祈る。
コメント