JWTの「none」アルゴリズムという悪夢:脆弱性の深層とアーキテクトが打つべき防衛線
JWT(JSON Web Token)は現代のWeb認証の標準だ。しかし、多くの開発者が「ライブラリがよしなにやってくれる」という幻想に依存し、その背後で蠢く低レイヤの設計ミスを放置している。
特に有名な alg: "none" 脆弱性は、もはやレガシーな攻撃手法として片付けられがちだが、現場のコードベースを監査すると、今なお「設定不備」という形で潜伏している。今日は、この脆弱性の本質的なメカニズムと、そこから派生する現代的な防御アーキテクチャについて、現場の泥臭い知見を交えて紐解いていこう。
—
1. なぜ「none」は死なないのか:プロトコル設計の罪
JWT(RFC 7519)の仕様には、署名検証をスキップするための none アルゴリズムが定義されている。これはテスト目的や、既にセキュアなチャネル(TLS)で保護された内部通信でのオーバーヘッド削減を意図したものだが、これが認証の入り口であるパブリックなAPIで有効化されていれば、攻撃者は一瞬でゲートを突破できる。
攻撃のメカニズム
攻撃者は、JWTのヘッダー部分を以下のように書き換える。
// 改ざんされたヘッダー
{
“alg”: “none”,
“typ”: “JWT”
}
サーバーサイドのライブラリが、この「ヘッダーで指定されたアルゴリズム」を盲目的に信頼し、検証ロジックをバイパスすれば、payload(クレーム)を任意に書き換えた偽造トークンが「検証済み」として受理される。これはプロトコルそのものの欠陥というより、「検証関数へ渡すアルゴリズムを動的に決定してしまう」という実装の不手際に起因する。
—
2. 現場で叩くべき「防御の鉄則」
チーフホワイトハッカーとして断言する。ライブラリの設定をデフォルトのままにしておくことは、自ら鍵を掛け忘れた金庫を公開するようなものだ。
防御の要諦:アルゴリズムの固定化
検証を行う際は、必ず「期待するアルゴリズム」をハードコードで指定すること。動的なアルゴリズム選択を許容するメソッド(例:jwt.verify(token))は避け、アルゴリズムを明示的に強制するメソッドを使用せよ。
Go言語による安全な検証実装例
import (
“github.com/golang-jwt/jwt/v5”
)
func ValidateToken(tokenString string, secretKey []byte) (jwt.Token, error) {
// パース時にアルゴリズムを明示的に指定する
return jwt.Parse(tokenString, func(token jwt.Token) (interface{}, error) {
// algフィールドが期待するものと一致するかチェック(重要)
if _, ok := token.Method.(jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf(“予期しない署名アルゴリズム: %v”, token.Header[“alg”])
}
return secretKey, nil
})
}
この実装では、攻撃者が alg: "none" を注入しても、SigningMethodHMAC へのキャストが失敗し、検証処理が即座に中断される。
—
3. 次世代の脅威:耐量子暗号とガードレイル
今、我々が直面しているのは、RSAやECDSAといった現在の公開鍵暗号が、将来的な量子コンピュータの脅威(Shorのアルゴリズム)に曝されるという未来だ。
JWTの署名検証においても、鍵の交換プロセスや暗号スイートの選定には、「暗号アジリティ(Crypto Agility)」が求められる。耐量子暗号(PQC)への移行期においては、従来のJWTの運用に加え、以下のアーキテクチャを検討すべきだ。
- ハイブリッド署名スキームの導入: 古典的なECDSAと耐量子署名アルゴリズム(Dilithium等)を組み合わせた二重署名。
- ガードレイルとしてのAPIゲートウェイ: アプリケーションレイヤでの検証を待たず、エッジ(API Gateway)でJWTのヘッダー構造を厳格に検査し、許可されていないアルゴリズムが含まれているパケットは即座にドロップする。
—
4. セキュリティアーキテクトへの提言
攻撃者は常に「ライブラリの裏側」を狙っている。メモリ安全性(Buffer Overflow)の観点からも、現在ではC/C++ベースの暗号ライブラリをラップしたJWTライブラリではなく、メモリ安全性が担保された言語(Rust等)で記述された jsonwebtoken 系のクレートを採用することが、監査上のベストプラクティスとなっている。
監査チェックリスト
1. アルゴリズム・ホワイトリストの徹底: システム内で許可するアルゴリズム(例: RS256 のみ)を定数として管理し、それ以外を即時拒否しているか。
2. キーのローテーション: 公開鍵暗号(RS256/ES256)を使用し、HS256(対称鍵)の共有リスクを排除しているか。
3. トークン・インスペクション: パケットキャプチャや監査ログで、alg: none が含まれる試行が検知された場合、即座にIDS/IPSで当該IPをブロックする自動応答が構築されているか。
「セキュリティは実装の細部に宿る」。JWTの検証ひとつとっても、それは単なる関数の呼び出しではなく、システムの信頼を支えるアーキテクチャの根幹だ。教科書を読み終えたら、次は自身のコードのメモリレイアウトと、ライブラリが内部で何を信じ込んでいるかを疑うことから始めてほしい。それが、プロのセキュリティエンジニアの歩みだ。
コメント