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

JWTの「none」脆弱性:なぜその実装は、玄関の鍵を自分で開けているのか

現場でコードレビューをしていると、今でもたまに遭遇するんだ。「JWTの検証ライブラリを使っているから大丈夫だ」という慢心。だが、そのライブラリが「どのアルゴリズムを信頼するか」という設定を開発者に委ねている場合、そこには巨大な落とし穴がある。

今日は、JWTの署名アルゴリズムを「none」に書き換えられてしまう、通称「alg: none」攻撃について、なぜこれが依然として脅威なのか、どうすれば二度と足元をすくわれない実装ができるのか、現場の視点で語ろうと思う。

1. なぜ「none」で認証が突破できるのか?

JWT(JSON Web Token)は、ヘッダー、ペイロード、署名の3つのパートで構成されている。通常、署名はサーバー側の秘密鍵を使って生成されるが、攻撃者がここで行うことは極めてシンプルだ。

1. ヘッダーの改ざん: `{ “alg”: “HS256” }` となっている部分を `{ “alg”: “none” }` に書き換える。
2. ペイロードの改ざん: `{“sub”: “user123”, “role”: “user”}` を `{“sub”: “admin”, “role”: “admin”}` に書き換える。
3. 署名の削除: JWTの仕様上、`alg: none` の場合、署名パートを空にすることが許容される。

もし、バックエンドの実装が「とりあえずライブラリの `verify` 関数を呼んでいるだけ」で、アルゴリズムの固定を行っていなければ、ライブラリはヘッダーを読み取り、「ふむ、アルゴリズムは `none` だな。じゃあ検証は不要だ」と判断して通してしまう。これぞ、玄関の鍵を「なし」に設定して、泥棒を招き入れている状態だ。

2. 実務で陥る「検証の怠慢」

多くのライブラリは、柔軟性のために「どのアルゴリズムを受け入れるか」というホワイトリストを設定するようになっている。これを指定せずにデフォルト値に頼るのが一番危ない。

悪い実装例(Python: PyJWT)

致命的:algの指定がないため、ヘッダーのアルゴリズムが何であれ受け入れてしまう
payload = jwt.decode(token, options={“verify_signature”: False})
あるいは、秘密鍵を渡しているだけでアルゴリズム指定を忘れているケース

3. 防御の鉄則:アルゴリズムを「固定」する

防御はシンプルだ。「このエンドポイントでは絶対にこのアルゴリズム以外認めない」とコードで宣言する。これが唯一にして絶対の解だ。

セキュアな実装サンプル(Node.js: jsonwebtoken)

開発現場で最もよく見るNode.jsでの実装例だ。`algorithms` オプションで、許可するアルゴリズムを配列で明示的に指定する。

const jwt = require(‘jsonwebtoken’);

const token = req.headers.authorization.split(‘ ‘)[1];
const secret = process.env.JWT_SECRET;

try {
// ポイント:algorithmsを明示的に指定し、noneを排除する
const decoded = jwt.verify(token, secret, {
algorithms: [‘HS256’] // 許可するアルゴリズムを固定。HS256以外は弾く
});

console.log(“認証成功:”, decoded);
} catch (err) {
// 署名が一致しない、または許可されていないアルゴリズムの場合はここで落ちる
console.error(“不正なトークンです:”, err.message);
}

セキュアな実装サンプル(PHP: firebase/php-jwt)

PHPでも同様だ。ライブラリのメソッドに対して、許容アルゴリズムを配列で渡す。

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

try {
// 第3引数に許容するアルゴリズムを明示的に定義
$decoded = JWT::decode($token, new Key($secretKey, ‘HS256’));
} catch (\Exception $e) {
// ログに詳細を残しつつ、ユーザーには汎用的なエラーを返す
error_log(“JWT検証失敗: ” . $e->getMessage());
http_response_code(401);
exit(“Unauthorized”);
}

4. インフラ層での二重ガード

アプリケーションコードの修正が最優先だが、もしレガシーなシステムで即時のコード修正が難しい場合、WAF(AWS WAF等)でJWTのヘッダーを検査することも検討すべきだ。

例えば、AWS WAFの文字列一致ルールで、リクエストヘッダー内のJWT文字列に `none` という文字列が含まれていないかをチェックするフィルターをかけることはできる。ただし、これはあくまで応急処置だ。JWTはBase64URLエンコードされているため、単純な文字列検索ではバイパスされる可能性があることを忘れてはいけない。

セキュリティチーフからの提言

「ライブラリのデフォルト」は、時にセキュリティの敵だ。エンジニアとしてコードを書くとき、常に自問自答してほしい。「もし攻撃者がヘッダーを書き換えたら、このコードはどう動く?」と。

  • アルゴリズムをハードコードせよ: 動的に決定させるな。
  • 例外処理を疎かにするな: 検証失敗時は即座にセッションを破棄し、ログに詳細を記録せよ。
  • 鍵の管理を厳格に: `none` 攻撃以外にも、公開鍵をJWTヘッダーに埋め込んで検証させる攻撃(Key Injection)もある。鍵は必ずサーバーの環境変数かKMSで安全に管理すること。

脆弱性は、知識があれば防げるものばかりだ。今日から自分のプロジェクトの `verify` 箇所を全検索してみてほしい。もし `algorithms` 指定が漏れていたら、それは今すぐ直すべき「技術的負債」だ。頑張ってくれ。

コメント

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