JWTの「署名検証バイパス」を笑い飛ばす:現場で使える堅牢な実装ガイド
エンジニア諸君、今日もセキュアなコードを書いているか?
ログイン後のセッション管理にJWT(JSON Web Token)を採用しているプロジェクトは多いだろう。ステートレスでスケーラビリティが高い、まさに現代のWeb開発の救世主だ。しかし、このJWT、実装のほんのわずかな「解釈の誤り」が、システム全権乗っ取りという最悪の悪夢を招くことを忘れてはいけない。
今日は、攻撃者が血眼になって探しているJWTの「穴」と、それを二度と通さないための防衛術を叩き込む。
—
1. 攻撃者が狙う「署名検証の盲点」
JWTの構造は単純だ。Header(アルゴリズム等の情報)、Payload(クレーム情報)、Signature(署名)の3パートから成る。脆弱性の多くは、この「署名が正しく検証されていない」という一点に集約される。
代表的な攻撃手法:アルゴリズムのすり替え
攻撃者はJWTのヘッダーにある alg(アルゴリズム)フィールドを書き換える。
1. none アルゴリズム攻撃:
ヘッダーを {"alg": "none"} に書き換え、署名部分を空にする。実装ライブラリが「署名なし」を許可するように設定されていると、サーバーはこれを「有効なトークン」として誤認する。
2. RS256からHS256への強制切り替え:
公開鍵暗号(RSA)を使っているシステムに対し、HS256(共通鍵暗号)への変更を強要する。攻撃者は、サーバーが公開鍵を「HMACの秘密鍵」として使ってしまうバグを突き、公開鍵そのものを署名キーとして署名を捏造する。
これらは理論上の話ではない。ライブラリのデフォルト設定を鵜呑みにした瞬間に、あなたのサーバーは「鍵なしで管理者権限を通すゲート」と化す。
—
2. 【防御策】署名検証を強固にする実装コード
ライブラリを使う際、最も重要なのは「アルゴリズムの固定」だ。外部からの入力を信用して動的にアルゴリズムを決定してはならない。
Pythonによるセキュアな実装例(PyJWT)
jwt.decode を使う際は、必ず algorithms 引数を明示的に指定すること。
import jwt
# 秘密鍵(環境変数から読み込むこと)
SECRET_KEY = "your-very-secret-key"
def verify_token(token):
try:
# ポイント: algorithmsをリストで明示的に指定する
# 'none'や他の脆弱なアルゴリズムを一切受け付けない
decoded = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"]
)
return decoded
except jwt.InvalidTokenError as e:
# ログには詳細を記録するが、ユーザーには汎用的なエラーを返す
print(f"セキュリティ警告: 不正なトークンが検出されました: {e}")
return None
JavaScript (Node.js) による実装例(jsonwebtoken)
こちらも同様に、期待するアルゴリズムを verify に渡す。
const jwt = require('jsonwebtoken');
const publicKey = process.env.PUBLIC_KEY; // 公開鍵
function verifyUser(token) {
try {
// RS256固定。鍵の不一致だけでなく、アルゴリズムの不一致も弾く
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
return decoded;
} catch (err) {
// 攻撃の予兆としてログを監視システムへ飛ばすこと
console.error("JWT検証失敗:", err.message);
throw new Error("Unauthorized");
}
}
—
3. インフラレベルでの防衛と注意点
コードが完璧でも、インフラの鍵管理がザルでは意味がない。以下のルールを鉄則とせよ。
- 鍵のローテーション:
SECRET_KEYをコードベースにハードコーディングするのは言語道断だ。AWS Secrets ManagerやHashiCorp Vaultを活用し、最低でも半年〜1年単位で鍵を更新せよ。 - WAFでの異常検知: JWTのヘッダーが頻繁に書き換わっているリクエストや、
noneという文字列が含まれるリクエストを検知したら、即座にIPごとブロックするルールをWAF(AWS WAF等)に組み込め。 - トークン寿命の最小化: リフレッシュトークンとアクセストークンを分離し、アクセストークンの有効期限は「数分から数十分」に設定せよ。万が一漏洩しても、被害は最小限に抑えられる。
—
最後に:エンジニアとしての心構え
「ライブラリが勝手にやってくれるだろう」という甘えが、数々のインシデントを生んできた。JWTは非常に強力だが、その実装には「ホワイトリスト方式の厳格な検証」が不可欠だ。
攻撃者は、我々が「デフォルト設定」で妥協している瞬間を狙っている。今日紹介したコードは、あくまで「最低限」の防衛ラインだ。自分の書いているコードが、どのアルゴリズムで、どの鍵を使って署名を検証しているのか。これを説明できないうちは、まだJWTを扱う資格がないと思ってくれ。
さあ、コードベースを今すぐ確認しろ。脆弱な実装が見つかったら、それが明日を変えるチャンスだ。幸運を祈る。
コメント