こんにちは!Webアプリケーションの開発やインフラの管理、本当にお疲れ様です。日々、新しい技術をキャッチアップしながら安全なシステムを作っていくのは、本当に大変ですよね。
今回は、最近のWebシステムでよく使われている「JWT(JSON Web Token)」という仕組みに潜む、ちょっぴり怖い落とし穴についてお話ししたいと思います。
「JWTって名前は聞いたことあるけど、なんだか難しそう……」という新人エンジニアの方や一般開発者の方に向けて、身近な「家の鍵」の例えを使いながら、一歩ずつ優しく紐解いていきますね。一緒にセキュリティの基礎を学んでいきましょう!
—
1. JWT(JSON Web Token)って、そもそも何だろう?
現代のWebアプリでは、私たちがログインした後に「私は〇〇です!」と証明するために、通行手形のようなものが使われます。これが JWT です。
例えば、あなたがホテルの会員になったと想像してください。フロントで本人確認を済ませると、名前や会員ランクが書かれた「プラスチック製の宿泊証明カード」をもらいますよね。
館内のお店や大浴場に行くとき、従業員はそのカードを見るだけで、あなたが本物の宿泊客だと確認できます。
JWTもこれとまったく同じです。サーバーが発行した「私は誰で、どんな権限を持っているか」が書かれたデジタルな証明書だと思ってください。
JWTの構造を覗いてみよう
JWTは、ドット(.)で区切られた3つのパートでできています。
1. Header(ヘッダー): どんな仕組み(アルゴリズム)で封をしているか
2. Payload(ペイロード): 「ユーザー名: yamada」「権限: 一般ユーザー」といった中身の情報
3. Signature(署名): 改ざんされていないことを証明する「ホテルのハンコ(電子署名)」
実物はこんな形をしています(実際にはもっと長いです)。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJyb2xlIjoidXNlciJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
—
2. 身近な例え:「ホテルのハンコ」と「偽造された合鍵」
ここで、今回のテーマである「署名検証の不備」と「アルゴリズムNone攻撃」を、ホテルのセキュリティに例えて考えてみましょう。
健全な状態(普通のセキュリティ)
ホテルのカードには、フロントの裏でしか押せない特別な「ハンコ(署名)」が押されています。
従業員はカードを見たとき、「お、ちゃんとしたホテルのハンコが押されているから本物だな」と安心してあなたを部屋に通します。もし、悪意ある人が自分で紙に「私は管理者です」と書いても、あの特別なハンコは真似できないので、すぐにニセモノだとバレますよね。
攻撃を受けている状態(アルゴリズムNone攻撃)
さて、ここでホテルのシステムに「うっかりミス」があったとします。
それは、「ハンコが押されていなくても、カードの形をしていて、中身が『管理者』って書いてあったら、そのまま信じちゃおう」という、信じられないほどお人よしな設定です。
悪意あるユーザー(泥棒)は、これを見逃しません。
彼は自分のカードをこっそり書き換えて、中身を「権限: 管理者」に変えました。さらに、ヘッダー部分に「このカードはハンコ不要(alg: none)です!」と書き込んでサーバーに提出しました。
お人よしなサーバーはこう言います。
「おっ、ヘッダーに『ハンコいらない』って書いてあるから、ハンコの確認はパスしよう! 中身も管理者って書いてあるし、君を管理者として通すね!」
……これが、アルゴリズムNone攻撃の正体です。ものすごくシンプルですが、システムのチェックが甘いと簡単に突破されてしまう恐ろしい手口なんです。
—
3. なぜこんなことが起きるの?(脆弱なコードの例)
実際のシステムで、どのような実装ミスがこの脆弱性を生んでしまうのでしょうか。Node.jsを使った簡単な例で見てみましょう。
以下のコードは、「送られてきたJWTの署名をちゃんと検証しているつもり」の危ないコードです。
const jwt = require('jsonwebtoken');
// 【危険な実装例】アルゴリズムの制限をしていない
function verifyTokenInsecure(token) {
try {
// 署名の検証を行っている「つもり」だが、
// 攻撃者がヘッダーに "alg": "none" を指定してくると、
// ライブラリの設定次第では検証をスキップしてしまうことがある!
const decoded = jwt.verify(token, 'your-secret-key');
return decoded;
} catch (err) {
return null; // 検証失敗
}
}
このコードの問題点は、「どんなアルゴリズムで署名されていても受け入れますよ」という状態になっている点です。攻撃者が alg: none や、非対称暗号(RS256)から対称暗号(HS256)へのすり替え(公開鍵の誤認)を行った際、サーバーがそれを許してしまいます。
—
4. しっかり対策をしよう!安全なコードの書き方
「じゃあ、どうやって防げばいいの?」安心してください、対策はとても明確です。
それは一言で言うと、「サーバー側で『このアルゴリズム以外は絶対に認めない!』と厳しく指定すること」です。
先ほどのNode.jsのコードを、安全な形に修正してみましょう。
const jwt = require('jsonwebtoken');
// 【安全な実装例】許可するアルゴリズムを明示的に指定する
function verifyTokenSecure(token) {
try {
const secretKey = 'your-secret-key';
// options の中で algorithms を必ず指定する!
// これにより、例えば "HS256" 以外のアルゴリズム("none" など)が
// 送られてきた場合に、強制的にエラー(拒否)にすることができます。
const options = { algorithms: ['HS256'] };
const decoded = jwt.verify(token, secretKey, options);
return decoded;
} catch (err) {
// 署名がおかしい、または許可されていないアルゴリズムの場合はここで弾かれます
console.error("不正なトークンです:", err.message);
return null;
}
}
このように、algorithms: ['HS256'] のようにホワイトリスト方式で利用を許可するアルゴリズムを明示しておけば、攻撃者がどれだけ巧妙に alg: none を仕込んできても、サーバーが「そんなルールは知らない!」とピシャリと跳ね返してくれます。
—
5. 実務で確認しておきたいチェックリスト
最後に、開発現場やインフラの構築で今すぐ見直せるチェックポイントをまとめました。
1. ライブラリのデフォルト設定を過信しない
- 使用しているJWTライブラリ(
jsonwebtoken,pyjwt,php-jwtなど)が、デフォルトでnoneを許可していないかドキュメントを確認しましょう。
2. アルゴリズムの固定(ホワイトリスト化)
- 検証時には必ず使用するアルゴリズム(
HS256やRS256など)をコード内で明示的に指定してください。
3. 鍵の管理を厳重に
- 対称鍵(HS256等)を使う場合のシークレットキーが、ソースコードにハードコーディング(直書き)されていないか確認し、環境変数などで安全に管理しましょう。
—
おわりに
今回は、JWTの署名検証不備とアルゴリズムNone攻撃について、身近な例えを交えて解説しました。
セキュリティの対策は、難解な呪文を覚えることではなく、「当たり前の確認を、システムにサボらせないこと」の積み重ねです。
「うっかり検証をスキップしちゃった」という小さなミスが、大きなセキュリティインシデントにつながることがあります。でも、今日学んだ「アルゴリズムの固定」というひと手間を知っていれば、もう大丈夫ですよね。
一歩ずつ、安全で堅牢なシステム作りを楽しんでいきましょう!それでは、また次の記事でお会いしましょう!
コメント