こんにちは!セキュリティの世界へようこそ。最高セキュリティ責任者(CISO)の「セキュリティの番人」です。
皆さんは「鍵」と聞くと、何を思い浮かべますか?家の玄関の鍵、車のスマートキー、あるいは秘密の宝箱の鍵かもしれませんね。デジタルの世界でも、ユーザーが「私は本人です!」と証明するために、さまざまな「電子の鍵」が使われています。
その中でも、最近のWeb開発で非常によく使われているのがJWT(JSON Web Token / ジョット)という仕組みです。しかし、この便利な鍵には、実は「泥棒が簡単に合鍵を作れてしまう」ような、ちょっとおかしな弱点が潜んでいることがあります。
今日は、初心者の方でも絶対に理解できる「JWTの『none』アルゴリズム脆弱性」とその防ぎ方について、身近な例え話を交えて楽しく解説していきます。一歩ずつ、安全なコードへの階段を登っていきましょう!
—
1. JWTは「お店の会員証」のようなもの
まず、JWTがどんなものかをおさらいしましょう。JWTは、大きく分けて3つのパーツがドット(.)で繋がった構造をしています。
1. ヘッダー(Header): 鍵の種類や、どうやって署名したか(例:AESやRSAなど)を書いた「ラベル」
2. ペイロード(Payload): 「名前:田中さん」「権限:一般」といった「中身の情報」
3. 署名(Signature): 中身が書き換えられていないかを証明する「ハンコ」
これを「会員証」に例えてみましょう。
「私は田中です(中身)」と書かれたカードに、お店の店長だけが持っている「特別なスタンプ(署名)」が押してある状態です。店員さんはそのスタンプを見て、「あ、これは店長が押した本物だ」と判断します。
—
2. 泥棒の必殺技:消える魔球ならぬ「消えるハンコ」
ここで恐ろしい脆弱性の話をします。それがalg: none(アルゴリズム:なし)という問題です。
JWTのルール(仕様)には、なんと「署名が必要ない場合」のための設定が存在します。それがヘッダーに書く alg: none です。
もし、Webサイトのプログラムが「ヘッダーに書いてある通りに動く」お人好しな店員さんだったらどうなるでしょうか?
1. 泥棒の作戦: 泥棒が自分の会員証の「名前」を「管理者」に書き換えます。
2. ヘッダーを改ざん: ヘッダーの「署名の種類」を none (署名なしでOK!)に変更します。
3. 署名を捨てる: 本来あるはずの複雑なスタンプを消してしまいます。
この偽造カードを店員さんに見せると、お人好しな店員さんはこう言います。
「えーっと、ヘッダーには『署名なしでいい』って書いてありますね。じゃあ、スタンプがなくてもこのカードは本物ですね!ようこそ、管理者さま!」
…これが、alg: none 脆弱性の正体です。泥棒が「自分を証明するルール」を勝手に決めてしまい、サーバーがそれを鵜呑みにしてしまうのです。
—
3. 防御の鉄則:店員の「マニュアル」を固定する
この攻撃を防ぐための対策は、実はとてもシンプルです。
それは、「カードに何が書いてあろうと、店員はあらかじめ決められた確認方法以外は認めない」というルールを徹底することです。
サーバー側で「私は HS256(特定の共通鍵暗号)というハンコしか認めません!」と、検証ロジックをガチガチに固めてしまうのです。
実践!安全なJWT検証コード例(JavaScript/Node.js)
ここでは、有名なライブラリである jsonwebtoken を使った例を見てみましょう。悪い例と良い例を比較すると、どこがポイントか一目瞭然です。
❌ 危険なコード(攻撃者の言いなり)
// ライブラリを読み込みます
const jwt = require('jsonwebtoken');
// ユーザーから送られてきたトークン
const token = "泥棒が作った偽造トークン";
// 【ダメな例】アルゴリズムを指定せずに検証している
// 攻撃者がヘッダーを { "alg": "none" } にすると、検証をパスしてしまう可能性があります
const decoded = jwt.decode(token);
✅ 安全なコード(アルゴリズムを強制する)
const jwt = require('jsonwebtoken');
// サーバーだけが知っている秘密の合鍵
const SECRET_KEY = "your-super-secret-key";
// ユーザーから送られてきたトークン
const token = "ユーザーからのトークン";
try {
// 【ここが重要!】
// 第3引数で 'algorithms' を明示的に指定します。
// これにより、たとえヘッダーに "none" と書かれていても、
// サーバー側で「いや、HS256以外は認めないよ」と突っぱねることができます。
const verifiedData = jwt.verify(token, SECRET_KEY, {
algorithms: ['HS256'] // 許可するアルゴリズムのホワイトリスト
});
console.log("認証成功!ユーザー情報:", verifiedData);
} catch (err) {
// 署名が合わない場合や、許可されていないアルゴリズム(noneなど)の場合はここに来る
console.error("認証失敗:怪しいトークンを検知しました!", err.message);
}
—
4. 現場で役立つ「防犯チェックリスト」
新人の皆さんが開発に携わる際は、以下の3つのポイントを心に刻んでおいてください。
1. 「ユーザーの言うこと」を信じない:
JWTのヘッダーは、ユーザー(攻撃者)が自由に書き換えられる場所です。「どうやって検証するか」という指示を、ユーザーに委ねてはいけません。
2. ホワイトリスト形式で許可する:
「none はダメ」と禁止リストを作るのではなく、「HS256 だけが良い」という許可リスト(ホワイトリスト)を作るのがセキュリティの鉄則です。
3. ライブラリを最新に保つ:
昔の古いライブラリは、デフォルトで none を受け入れてしまうものがありました。常に最新のライブラリを使い、設定を確認する癖をつけましょう。
—
結びに:セキュリティは「おもてなし」の延長線上にある
セキュリティと聞くと「難しそう」「面倒くさい」と感じるかもしれません。でも、実は「大切なお客様の情報を守り、泥棒からお店を守る」という、とても人間味のあるお仕事なんです。
今回の none アルゴリズム対策は、言わば「玄関の鍵の種類を、家主であるあなたがしっかり決める」という当たり前のステップです。一つひとつの設定の意味を理解していけば、あなたはもう立派な「防衛隊員」の一員です。
もし、開発中に「これって本当に安全かな?」と迷ったら、いつでもこの記事を思い出してください。一歩ずつ、一緒に安全なWebの世界を作っていきましょう!
これからも応援しています!
—
最高セキュリティ責任者 / ホワイトハッカー
「セキュリティの番人」より
コメント