「none」アルゴリズムの罠:JWT実装に見る、認証の根幹を揺るがす設計の盲点
セキュリティ・アーキテクトとして数多のインシデントレスポンスを手がけてきた経験から断言できる。現代のWebアプリケーションにおいて、最も「安易に実装され、かつ最も致命的な結果を招く」箇所の筆頭は、JWT(JSON Web Token)の検証ロジックだ。
多くの開発者が、ライブラリの利便性に甘んじ、その裏側に潜む「プロトコル仕様上の不備」を看過している。今回は、JWTの alg: none 脆弱性を切り口に、なぜこの程度の「初歩的なミス」が未だに防げないのか、そして我々がどう設計を強固にすべきかを深掘りする。
—
1. 署名検証を無力化する「none」の正体
JWTがRFC 7519で定義された際、意図的に実装されたのが alg: none というアルゴリズムだ。これは「署名を行わない」ことを明示的に指定するためのものだが、攻撃者にとっては「認証の全否定」を意味する鍵となる。
攻撃のメカニズム
攻撃者はパケットをキャプチャし、Base64URLエンコードされたJWTのヘッダーを次のように書き換える。
// 改ざん前のヘッダー
{“alg”: “HS256”, “typ”: “JWT”}
// 改ざん後のヘッダー
{“alg”: “none”, “typ”: “JWT”}
ライブラリの検証関数が alg ヘッダーを信頼し、かつ「none」を許可する設定のまま放置されていれば、バックエンドは「署名は不要である」と判断し、後続のペイロードを無条件で信頼してしまう。これが、権限昇格(Privilege Escalation)の最も原始的かつ強力なエントリーポイントだ。
—
2. なぜ「ライブラリのデフォルト」を信じてはいけないのか
多くの開発者は、ライブラリが提供する verify() メソッドに引数を渡すだけで安心する。しかし、多くのJWTライブラリは、後方互換性や柔軟性を保つために、デフォルトで複数のアルゴリズムを許容する設計になっていることが多い。
脆弱な実装例(Node.js: jsonwebtoken のアンチパターン)
// 【警告】この実装は非常に危険です
const jwt = require(‘jsonwebtoken’);
// アルゴリズムを明示的に制限していないため、攻撃者がヘッダーで「none」を指定すると
// それを許可してしまう可能性がある
const decoded = jwt.verify(token, secretKey);
推奨される堅牢な実装
検証時には、「期待するアルゴリズムのみを許可する」というホワイトリスト方式を強制しなければならない。
// 【推奨】期待するアルゴリズムを明示的に固定する
const jwt = require(‘jsonwebtoken’);
try {
const decoded = jwt.verify(token, secretKey, {
algorithms: [‘RS256’] // 署名検証アルゴリズムをRS256のみに制限
});
} catch (err) {
// 署名不一致やアルゴリズムの不一致はここで確実にハンドリング
console.error(“認証失敗: 不正なトークンまたはアルゴリズム”);
}
—
3. 公開鍵暗号(RS256/ES256)への完全移行と運用上の注意
HS256(共通鍵)は、鍵が漏洩した瞬間に全トークンの偽造が可能になるという単一障害点を持つ。対して、RS256(非対称鍵)を採用すれば、認証サーバー(IdP)のみが秘密鍵を持ち、リソースサーバーは公開鍵のみで検証を行うという分離が可能になる。
ここでセキュリティ・アーキテクトが意識すべきは、「鍵のライフサイクル管理」だ。
- JWKS (JSON Web Key Set) の運用: 公開鍵をエンドポイントで公開し、定期的に自動ローテーションさせる。これにより、秘密鍵が万が一漏洩しても、古い鍵を無効化するまでの時間を最小化できる。
- 耐量子暗号(PQC)の検討: 今後、Shorのアルゴリズムが実用的な量子計算機上で動くようになれば、現在のRSAやECDSAは崩壊する。今のうちから、ハイブリッド署名(古典暗号と耐量子署名の併用)のライブラリ実装を注視しておくべきだ。
—
4. 防衛層の設計:ガードレイルとしてのAPIゲートウェイ
アプリケーションコードの修正は、開発サイクルの都合上どうしても遅れが生じる。そのため、「防御は多層化(Defense in Depth)」せよ。
1. APIゲートウェイでのバリデーション: 認証済みトークンかどうかのチェックをアプリ内で行う前に、KongやEnvoyなどのゲートウェイ側で alg ヘッダーを検査し、ホワイトリストにないアルゴリズムが含まれていれば即座にパケットを破棄する。
2. プロンプトインジェクションへの応用: 今後のAI活用を見据えれば、JWTペイロード内にユーザーのパーミッションやカスタム属性を埋め込む際、それがLLMのプロンプトに直結する設計は避けるべきだ。JWTは「誰であるか」の身分証明書であり、「何ができるか」の指示書としては扱うな。
—
結び:セキュリティは「性悪説」から始まる
最後に一つ、現場のテックリードたちへ伝えたい。
「ライブラリのドキュメントに書いてある通りに動く」と信じるのは、エンジニアとしては楽だが、セキュリティ担当としては怠慢だ。
パケット構造を覗き、ヘッダーを改ざんし、ライブラリが例外を投げるまでを自分の手で検証する。その泥臭いプロセスを省いた実装に、信頼を置くことはできない。セキュリティとは、コードの行数ではなく、「攻撃者がどこを突いてくるか」を想像する解像度の高さによって担保されるものだ。
君たちの設計が、常に攻撃者の思考の二歩先を行くことを期待している。
コメント