【実務・中級編】 JWT(JSON Web Token)の署名アルゴリズム ‘none’ 脆弱性の回避 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

JWTの「alg: none」脆弱性:その一行の油断が、システムを崩壊させる

現場でインシデント対応をしていると、往々にして「なぜこんな初歩的な……」という箇所で攻撃が成功している事実に直面します。特に、認証の要であるJWT(JSON Web Token)において、alg: none を許容してしまう脆弱性は、現代のWebアプリケーションにおける「玄関の鍵を最初から開けておく」に等しい愚行です。

今日は、なぜこの脆弱性が恐ろしいのか、そして実務で「絶対に踏み抜かない」ための確実な防御策を、泥臭い実装レベルで解説します。

—

1. なぜ「alg: none」が許されてしまうのか

JWTは、Header(アルゴリズム情報), Payload(ユーザー情報), Signature(署名)の3部で構成されます。攻撃者の狙いは単純です。

1. JWTのヘッダーにある alg を none に書き換える。
2. Signature を空にする(または削除する)。
3. Payload の sub(ユーザーID)や role を admin に書き換える。

もしサーバー側が、「ヘッダーに書かれているアルゴリズムをそのまま信じて検証処理を行っている」場合、サーバーは「お、署名なし(none)のトークンだな。じゃあ検証はパスだ」と判断し、偽造された管理者権限をいとも簡単に受け入れてしまいます。

攻撃のロジック(PoC)

攻撃者は以下のようなJSONをBase64URLエンコードし、トークンとして送りつけます。

// ヘッダー: アルゴリズムをnoneに改竄
{"alg": "none", "typ": "JWT"}

// ペイロード: 権限を昇格
{"sub": "123456", "role": "admin", "exp": 9999999999}

このトークンを投げ込まれたとき、検証ロジックが「ヘッダーの alg を見てから検証関数を決める」という設計になっていると、脆弱性の入り口が確定します。

—

2. 鉄則:アルゴリズムは「選択」ではなく「強制」する

防御の基本はシンプルです。「クライアントからの自己申告を信じない」こと。サーバー側で許可されたアルゴリズム(例えば RS256 や ES256)をハードコードし、それ以外は問答無用で拒否する検証ロジックを組みます。

Pythonでのセキュアな実装例(PyJWT使用)

Pythonの PyJWT ライブラリを使用する場合、algorithms 引数を明示的に指定することが重要です。

import jwt

def verify_token(token, public_key):
    try:
        # ポイント: 許可するアルゴリズムを明示的に指定する
        # これにより、ヘッダーに 'none' があっても、RS256以外は例外が発生する
        decoded = jwt.decode(
            token, 
            public_key, 
            algorithms=["RS256"] 
        )
        return decoded
    except jwt.InvalidAlgorithmError:
        # ここでログを出力し、不正なアルゴリズムの使用を検知する
        print("警告: 許可されていないアルゴリズムが使用されました")
        return None
    except Exception as e:
        return None

Node.js (jsonwebtoken) での実装例

Node.jsでも同様です。ライブラリ側で algorithms を指定しないと、デフォルト設定によっては脆弱性が残る可能性があるため、必ず配列で指定します。

const jwt = require('jsonwebtoken');

const verifyToken = (token, publicKey) => {
  try {
    // algorithms を明示的に指定し、none や HS256 へのダウングレード攻撃を防ぐ
    const decoded = jwt.verify(token, publicKey, { 
      algorithms: ['RS256'] 
    });
    return decoded;
  } catch (err) {
    // 検証失敗時はログを記録し、セッションを無効化する
    console.error('JWT検証失敗:', err.message);
    throw new Error('Unauthorized');
  }
};

—

3. インフラ層での多重防御(Defense in Depth)

アプリケーションコードの修正が前提ですが、万が一の「実装漏れ」を防ぐために、WAFやリバースプロキシでの監視も推奨します。

例えば、Nginxで特定の文字列が含まれるリクエストを監視する場合:

# Nginxの設定例: alg:none を含むリクエストを検知してログに落とす
location /api/ {
    if ($http_authorization ~* "eyJhbGciOiJub25lI") {
        return 403; # noneを含むJWTは即座に拒否
    }
    proxy_pass http://backend_app;
}

※ eyJhbGciOiJub25lI は {"alg":"none" をBase64URLエンコードした先頭部分です。

—

4. チーフエンジニアからの提言

「ライブラリがよしなにやってくれるだろう」という思い込みが、最も高いコストを支払うバグを生みます。JWTを扱う際のチェックリストをチームで共有してください。

1. アルゴリズムは常にホワイトリストで管理する: 動的にヘッダーから決定してはならない。
2. ライブラリのアップデートを怠らない: 古いバージョンには脆弱性が残っている可能性がある。
3. 公開鍵と秘密鍵の管理: RSAやECDSAを使用し、HS256(共通鍵)のような「鍵の漏洩=即全権限掌握」となるアルゴリズムの使用を避ける(可能な場合)。

セキュリティは「完成」することのないプロセスです。今日書いたこのコードが、明日のあなた自身のシステムを守る盾になることを忘れないでください。何か質問があれば、いつでもコードレビューの依頼を待っていますよ。

コメント

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