JWTの「none」アルゴリズムという悪夢:なぜあなたの認証は突破されるのか
エンジニアの諸君、認証の実装でJWT(JSON Web Token)を使っているだろうか? 「ステートレスでスケーラブルだから」という理由で採用したその技術が、実はセキュリティの最大のアキレス腱になっているかもしれない。
今日は、私がインシデント対応の現場で何度も見てきた、「JWTのアルゴリズム指定の脆弱性(CVE-2015-9235等に代表される実装不備)」について話そう。教科書には「検証せよ」としか書かれていないが、実戦では「何を・どう」検証するかがすべてだ。
1. なぜ「none」攻撃は成功してしまうのか?
JWTの構造は {Header}.{Payload}.{Signature} だ。本来、サーバーは署名(Signature)を検証することで、ペイロードが改ざんされていないことを確認する。
しかし、攻撃者はヘッダーを以下のように書き換える。
{
“alg”: “none”,
“typ”: “JWT”
}
ここで多くの開発者が陥る罠が、ライブラリのデフォルト設定だ。一部の古いライブラリや設定不備のある実装では、alg: none を「署名検証を行わない」という仕様として律儀に受け取ってしまう。つまり、攻撃者がヘッダーとペイロードを適当に書き換え(user_id: 1 を user_id: admin にする等)、署名部分を削除して送るだけで、サーバーは「署名なしでOK」と判断して認証を通してしまうのだ。これは鍵のない扉に自ら鍵穴を壊して放置しているようなものだ。
2. 【Python/PyJWT】正しいセキュア実装のサンプル
現場で最も多いのが、「とりあえず jwt.decode() を呼ぶ」だけのコードだ。これでは甘い。受け入れるアルゴリズムを明示的にホワイトリスト化し、署名検証を強制する必要がある。
以下は、PyJWTを用いたセキュアな実装だ。
import jwt
def verify_token(token, public_key):
try:
# 重要なポイント:
# 1. algorithms引数で「HS256」や「RS256」など、使用するものだけを明示する。
# 2. ここに “none” を含めてはならない。
decoded = jwt.decode(
token,
public_key,
algorithms=[“RS256”], # ここでアルゴリズムを厳格に固定
options={“require”: [“exp”, “iat”]} # 期限切れチェック等も強制
)
return decoded
except jwt.InvalidAlgorithmError:
# 意図しないアルゴリズムが指定された場合は即座に遮断
log.error(“不正なアルゴリズムが検出されました”)
return None
except jwt.ExpiredSignatureError:
log.error(“トークンの有効期限が切れています”)
return None
3. 【Node.js/jsonwebtoken】の鉄板設定
Node.js界隈でも同様だ。jsonwebtoken ライブラリを使う際、アルゴリズム指定を忘れると、攻撃者に付け入る隙を与える。
const jwt = require(‘jsonwebtoken’);
const verifyToken = (token) => {
try {
// algorithms配列を指定することで、none攻撃を完全に無効化する
const decoded = jwt.verify(token, process.env.PUBLIC_KEY, {
algorithms: [‘RS256’]
});
return decoded;
} catch (err) {
// 検証エラー時は詳細を返しすぎないこと(列挙攻撃対策)
console.error(“JWT検証失敗:”, err.message);
throw new Error(“Unauthorized”);
}
};
4. 運用サイドで防ぐ:WAFとアーキテクチャの視点
コード修正が間に合わないレガシーなシステムを守る最後の砦は、インフラ層だ。もしWebアプリケーションへのリクエストをNginxやWAF(AWS WAF等)でフィルタリングできるなら、Authorization ヘッダーの中身を解析して、"alg": "none" という文字列が含まれるリクエストを即座に403で返すべきだ。
Nginxによる簡易フィルタリング例:
(※正規表現による複雑な解析は推奨しないが、緊急回避としては有効)
JWTのヘッダー部分(Base64URLエンコードされた文字列)に
“alg”:”none” が含まれているかを確認(非常に安直な対策だが、攻撃の多くはこれで止まる)
if ($http_authorization ~ “eyJhbGciOiJub25lI”) {
return 403;
}
最後に:セキュリティの現場から君たちへ
JWTの脆弱性は、「ライブラリがよしなにやってくれる」というエンジニアの慢心から生まれる。
1. デフォルトを信じるな:ライブラリが何を受け入れるのか、ソースコードを追いかけて確認すること。
2. アルゴリズムを固定せよ:アプリケーションが必要とする署名アルゴリズム以外は、一切の入力を拒否する設定をコードベースで明示する。
3. 公開鍵暗号(RS256/ES256)を使え:HS256(共通鍵)は鍵の管理コストが高く、流出リスクが高い。可能なら非対称暗号を採用し、署名生成と検証の責務を分離せよ。
認証はシステムの心臓部だ。ここを疎かにするエンジニアに、未来のシステムを任せることはできない。今日から自分のコードを見直し、alg: none が入り込む余地を完全に消し去ってほしい。それがプロの仕事だ。
コメント