現場の諸君、お疲れ様。今日は「JWT(JSON Web Token)の署名偽造」という、攻撃者にとっての「マスターキー」を手に入れる悪夢のようなシナリオについて話そう。
多くのエンジニアは「JWTを使っているから大丈夫」と過信しているが、その実、秘密鍵の管理が杜撰な現場を私はいくつも見てきた。もしサーバーサイドの秘密鍵が漏れたら、システム上の全ユーザーは「管理者」になり得る。今日はその裏側と、二度とそんなミスを犯さないための防御策を叩き込む。
—
1. なぜ「秘密鍵の漏洩」がゲームオーバーなのか
JWTの仕組みを思い出してくれ。Header, Payload, Signatureの3段構成だ。このうちSignatureは、HeaderとPayloadをサーバーだけが知る秘密鍵でハッシュ化したものだ。
もし攻撃者がこの秘密鍵を入手すれば、「自分自身で署名を再計算できる」ことになる。つまり、Payloadの user_id を admin に書き換え、新しい署名を生成して送りつければ、サーバーはそれを「正当なトークン」として鵜呑みにするんだ。これは脆弱性というより、鍵を玄関のマットの下に置いておくのと同じレベルの過失だ。
—
2. 実践的攻撃手法:秘密鍵漏洩時の署名偽造 (Python PoC)
攻撃者は、環境変数やリポジトリに紛れ込んだ鍵を手に入れ、以下のようなスクリプトで管理者権限を奪取する。
import jwt
# 漏洩した秘密鍵(攻撃者が入手)
SECRET_KEY = "super-secret-key-that-should-not-be-in-git"
# 偽造したいペイロード(管理者権限を付与)
payload = {
"user_id": "admin",
"role": "superuser",
"exp": 1999999999 # 遠い未来まで有効
}
# 秘密鍵を使って署名を生成
forged_token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"Generated Token: {forged_token}")
# このトークンをCookieやAuthorizationヘッダーにセットしてリクエストするだけだ
このコードが示す通り、鍵さえあれば、権限管理のロジックをバイパスして即座にフルアクセスが可能になる。
—
3. 「絶対にやってはいけない」管理と、正しい防御策
鍵管理でよくある「死ぬ」パターンを挙げる。
- ソースコードへの直書き: GitHubにプッシュされた瞬間に公開される。
- 環境変数のログ出力: エラーログやデバッグ画面に
process.envがそのまま出力される。 - 鍵のローテーションなし: 数年間同じ鍵を使い続け、漏洩の検知が不可能。
【防御策】鍵の分離とローテーション
最も確実なのは、「鍵をアプリケーションコードから切り離す」ことだ。AWS Secrets Manager や HashiCorp Vault を使い、動的に鍵を取得させるのがプロのやり方だ。
推奨するNode.jsでの検証実装例
jsonwebtoken を使う際も、環境変数を直読みせず、非同期で鍵を管理する設計にすべきだ。
const jwt = require('jsonwebtoken');
// 鍵は直接書かず、セキュアなストレージから取得する想定
async function verifyToken(token) {
try {
// 環境変数ではなく、Secrets Manager等から取得した値を読み込む
const secret = await getSecretFromVault("JWT_SECRET");
// 署名アルゴリズムをHS256(対称鍵)からRS256(公開鍵/秘密鍵)に変えるのがベスト
// HS256は秘密鍵が漏れたら終わりだが、RS256なら公開鍵を配るだけで済む
return jwt.verify(token, secret, { algorithms: ['RS256'] });
} catch (err) {
throw new Error("不正な署名です");
}
}
—
4. インフラレベルでの防御:WAFとNginxの設定
アプリケーションの修正だけでなく、インフラ側でも「異常なトークン」を弾く準備をしておこう。Nginxで特定の文字列を含むヘッダーを監視したり、WAFで異常な長さのトークンをブロックする設定は有効だ。
# Nginxの設定例:JWTのフォーマットチェック
# あまりに不自然なトークンはアプリケーションに渡す前に弾く
if ($http_authorization ~* "Bearer\s+([a-zA-Z0-9-_=]+)\.([a-zA-Z0-9-_=]+)\.([a-zA-Z0-9-_=]+)") {
# 適切なトークン構造か確認する簡易的なロジック
# 本来はバックエンドでやるべきだが、多層防御の観点から
}
—
現場のエンジニア諸君へ:最後に
JWTの秘密鍵漏洩は、一度起きれば「どのトークンが偽造されたか」を特定するのは非常に困難だ。
1. HS256(共通鍵)は捨てろ: 公開鍵方式である RS256 や ES256 に移行せよ。これなら秘密鍵は認証サーバーだけが持ち、APIサーバーは公開鍵のみで検証できる。
2. 鍵はコードに入れるな: Secrets Managerのような「鍵管理サービス」を使うのが現代の常識だ。
3. ローテーションを自動化せよ: 鍵を「変える」ことが面倒な設計になっているなら、それが最大の脆弱性だ。
セキュリティは「完璧な防御」ではなく、「侵害されたときの被害を最小化する設計」だ。今日から、君のプロジェクトの鍵管理を一度見直してほしい。それがインシデントを防ぐ第一歩になる。
コメント