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)」を実装のルールとしてチームに徹底させてほしい。
何かあれば、また聞きに来い。現場の最前線で、一緒にシステムを守り抜こう。
コメント