【実務・中級編】JWTの署名アルゴリズム ‘none’ 脆弱性と検証の不備 – アプリケーションセキュリティ & 安全な開発防御ガイド

JWTの「none」アルゴリズムという悪夢:なぜあなたの認証は破られるのか

やあ。現場で泥をすすりながらインシデント対応をしていると、技術の進歩がいかに「攻撃者にとっての扉」を広げているかを痛感する。

今日話したいのは、JWT(JSON Web Token)の署名アルゴリズム「none」脆弱性だ。これ、数年前に大流行した手法だが、未だに「とりあえずライブラリのメソッドを呼んでおけば安全だろう」と高を括っている若手エンジニアのコードに潜んでいる。

結論から言えば、署名の検証を疎かにしたJWTの実装は、玄関の鍵をかけずに「鍵はかかっています」と張り紙をしているのと同じだ。今日は、この脆弱性がなぜ生まれ、どう防ぐべきかを、実戦的なコードと共に叩き込む。

—

1. なぜ「none」攻撃が成立するのか

JWTの構造は {ヘッダー}.{ペイロード}.{署名} だ。本来、署名は「そのトークンが正当なサーバーによって発行されたこと」を証明する。

しかし、攻撃者はヘッダーを書き換える。

{
“alg”: “none”,
“typ”: “JWT”
}

そして、署名部分を空にするか削除する。もしバックエンドのライブラリが「ヘッダーで指定されたアルゴリズム」を盲目的に信用し、none が指定された際に「署名検証をスキップする」仕様になっていれば、攻撃者はペイロード(user_id: 1 など)を自由に改ざんして管理者権限を奪取できる。

これはライブラリが「柔軟性」を重視するあまり、「安全性」を犠牲にした結果だ。

—

2. 実戦的PoC:攻撃者の視点

攻撃者は以下のようにトークンを捏造する。

1. JWTをデコード: 現在のトークンを取得。
2. ペイロード改ざん: {"sub": "admin", "iat": 1516239022} に変更。
3. ヘッダー変更: "alg": "none" に書き換え。
4. Base64エンコード: 署名を削除して送信。

これでバックエンドが none を許可していれば、認証突破だ。WAFで特定の文字列を弾くのも手だが、本質的な解決にはならない。

—

3. 防御の鉄則:厳格な検証実装

防御の鍵はただ一つ。「ライブラリのデフォルトを信じるな。アルゴリズムを明示的に強制せよ」だ。

Pythonでの実装例 (PyJWT使用)

PythonでJWTを扱うなら、pyjwtライブラリを使うのが一般的だが、検証時に必ず使用アルゴリズムを制限すること。

import jwt

危険な実装:アルゴリズムを指定しないと、攻撃者の指定した’none’が通る可能性がある
decoded = jwt.decode(token, options={“verify_signature”: False})

安全な実装:署名検証を必須にし、許可するアルゴリズムをHS256やRS256に限定する
def secure_verify(token, secret_key):
try:
# algorithmsを明示的に指定することが最大の防御
payload = jwt.decode(token, secret_key, algorithms=[“HS256”])
return payload
except jwt.InvalidAlgorithmError:
# noneなどが渡された場合、ここで弾かれる
print(“警告: 許可されていないアルゴリズムが検出されました”)
return None
except jwt.ExpiredSignatureError:
print(“トークンの有効期限切れ”)
return None

Node.jsでの実装例 (jsonwebtoken使用)

const jwt = require(‘jsonwebtoken’);

const publicKey = ‘—–BEGIN PUBLIC KEY—–\n…’;

// verifyメソッドでalgorithmsオプションを渡すのを忘れるな
// これを忘れると、ヘッダーのalgを読み取って処理が分岐してしまう
function verifyToken(token) {
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: [‘RS256’] // RS256以外はすべて拒否する
});
return decoded;
} catch (err) {
console.error(‘検証失敗:’, err.message);
return null;
}
}

—

4. セキュリティチーフからのアドバイス

コードレベルの修正に加え、インフラ側でも以下の対策を講じているか確認してくれ。

1. 非対称鍵(RS256 / ES256)の使用: サーバー同士で秘密鍵を共有するHS256(共通鍵)よりも、秘密鍵を漏洩させにくい公開鍵暗号方式を推奨する。
2. ライブラリの定期更新: npm audit や pip-audit をCI/CDパイプラインに組み込むのはもはや義務だ。古いライブラリには、この種の脆弱性が放置されていることがある。
3. Payloadに機密情報を持たせない: JWTはあくまで「身分証明書」だ。パスワードや個人情報は含めず、検証に必要な最小限のデータに留めろ。

最後に

セキュリティとは「一度設定して終わり」のタスクじゃない。攻撃者は常に「ライブラリの仕様の隙間」を探している。

「便利だから」「動くから」という理由でライブラリのデフォルト設定をそのまま使うのは、プロのエンジニアとして恥じるべき怠慢だ。検証ロジックには常に「疑いの目」を持ち、「アルゴリズムの固定(Hard-coding)」を実装のルールとしてチームに徹底させてほしい。

何かあれば、また聞きに来い。現場の最前線で、一緒にシステムを守り抜こう。

コメント

タイトルとURLをコピーしました