こんにちは!アプリ開発やWebサービスの運用、お疲れ様です。セキュリティの勉強をしていると、専門用語が次から次へと出てきて「うっ……」と頭が痛くなりますよね。
でも、大丈夫です!一歩ずつ、身近な例えから紐解いていけば必ず理解できます。今回は、今や現代のWebアプリで必須となっている「JWT(JSON Web Token)」の、ちょっと危ない落とし穴と、その対策について一緒に見ていきましょう。
—
1.JWT(JSON Web Token)って、身近なもので言うと何?
まず、JWTがどんなものかイメージするために、私たちの身の回りにある「あるもの」に例えてみましょう。
想像してみてください。あなたは今、とあるマンションの管理システムを作っています。住人がログインすると、システムは住人に対して「通行手形(カードキーのようなもの)」を発行します。
この通行手形の中には、次のような情報が入っています。
- 「部屋番号:101号室」
- 「名前:山田さん」
- 「権限:一般住人」
住人がエレベーターに乗ったり、共用ラウンジに入ったりするとき、この通行手形をセンサーにかざしますよね。システム側は手形を見て、「あ、101号室の山田さんだね、通っていいよ」と判断します。
この「デジタルな通行手形」こそが、JWTの正体です。
泥棒がやってくる!手形の偽装工作
さて、ここで少し悪知恵が働く泥棒(攻撃者)を想像してみましょう。
泥棒は「一般住人」の部屋に住んでいるけれど、どうしても「管理人(管理者権限)」専用のフロアに行きたいと思っています。
もし、この通行手形がただの紙きれで、誰でもマジックで「山田(一般)」を「佐藤(管理人)」に書き換えられたとしたらどうでしょう? そう、簡単に不正侵入できてしまいますよね。
だからこそ、本物の通行手形には、管理人の「絶対に戻せない消えないハンコ(署名)」が押してあります。
[ 手形の中身(部屋番号や名前) ] + [ 管理人のハンコ(署名) ] = JWTの完成!
システムは、手形を受け取ったときに必ず「このハンコは本物か?」を確認します。これが「署名検証」と呼ばれる仕組みです。このハンコのおかげで、中身を勝手に書き換えられても「偽物だ!」とすぐに見抜けるわけですね。
—
2.「ハンコを確認しなくていいよ!」という大バグ(Noneアルゴリズム攻撃)
さて、ここからが今日の本題であり、レッドチーム(攻撃側)が真っ先にチェックするポイントです。
実は、JWTの仕組み(仕様)には、ちょっとした「融通の利きすぎる落とし穴」が用意されていました。それは、「今回はハンコの確認を省略してもいいよ(Noneアルゴリズム)」というルールです。
開発初期のテスト段階や、「とりあえず動かしたい!」という急ぎの開発のとき、ハンコ(署名)のチェック機能をオフにしてしまう開発者さんがたまにいらっしゃいます。
これを現実のマンションに例えると、管理人がうっかり「今から、誰がどんな手形を持ってきても、ハンコの確認をしなくていいです!自己申告を信じます!」という看板を入り口に掲げてしまったような状態です。
攻撃者はどうやって侵入するのか?
泥棒はこの仕様の隙を突きます。手順はこうです。
1. 普通にログインして、自分がもらった本物の手形(JWT)を手に入れる。
2. その手形の中身を「一般住人」から「管理人」に自分で書き換える。
3. 手形の最後に、「今回はハンコのチェックは不要(none)」という合言葉をこっそり書き足して、システムに提出する。
4. システムは「あ、今回はハンコ確認しなくていいんだな! じゃあ管理人さん、どうぞ!」と、まんまと通してしまう。
これが、「JWTの署名検証不備(Noneアルゴリズム攻撃)」の正体です。ものすごくシンプルですが、実務ではいまだに発生する危険な脆弱性です。
—
3.安全なシステムを作るための防御策
「うわ、怖い!じゃあどうやって守ればいいの?」と思いますよね。安心してください。対策はとても明確です。
一歩ずつ、具体的なコードや設定を見ていきましょう。
対策1:「None」を絶対に受け付けないように設定する
一番大事なのは、JWTを検証するライブラリに対して、「none というアルゴリズム(ハンコなし)が来たら、問答無用でエラーにしなさい!」と厳しく言い聞かせておくことです。
例えば、よく使われるNode.jsのライブラリ(jsonwebtokenなど)では、検証時に次のようにアルゴリズムを明示的に指定します。
const jwt = require('jsonwebtoken');
// 危険な例:アルゴリズムの制限をしていない
// jwt.verify(token, secretKey);
// 安全な例:使用を許可するアルゴリズムを「HS256」などに厳密に限定する
try {
const decoded = jwt.verify(token, secretKey, {
algorithms: ['HS256'] // ここで許可する方式を明示!「none」を排除します
});
console.log("認証成功!権限:", decoded.role);
} catch (err) {
console.error("不正なトークンです!アクセス拒否!", err.message);
}
このように、利用するアルゴリズムをホワイトリスト形式でしっかり縛っておくことで、勝手に none に書き換えられても、ライブラリ側が「そんなハンコ(方式)は許可していません!」と弾いてくれるようになります。
対策2:公開鍵暗号方式(RS256)への切り替えを検討する
「ハンコ(秘密の合言葉)」をサーバーが1つだけ持っている方式(HS256など)だと、もしその合言葉が外部に漏れたとき、誰でも本物のハンコが作れてしまいます。
そこで、もう少し堅牢な仕組みとして、「鍵を分ける(RS256など)」という方法があります。
- 秘密鍵(プライベートキー): ハンコを押す人(認証サーバー)だけがこっそり持っている。絶対に外に出さない。
- 公開鍵(パブリックキー): ハンコが本物かチェックする人(APIサーバーなど)みんなに配る。
これなら、たとえチェック用の公開鍵が盗まれても、誰も「本物のハンコ」を新しく押すことはできません。マンションの例えで言うなら、「合鍵(秘密鍵)」は金庫にしまい、「本物判定チェッカー(公開鍵)」だけを各入り口の自動ドアに配るようなイメージです。
—
まとめ:一歩ずつ安全な開発を
今回は、JWTの「Noneアルゴリズム攻撃」の仕組みと、その防ぎ方を身近な例えを交えて解説しました。
- 攻撃の要点: サーバー側がハンコの確認をサボる(
noneを許容する)設定になっていると、中身を書き換えて不正侵入されてしまう。 - 防御の要点: ライブラリの設定で
algorithms: ['HS256']のように許可する方式を厳しく絞り込み、noneを絶対に受け付けないようにする。
セキュリティの対策は、最初は大げさ面倒に感じるかもしれませんが、基本のルールさえ押さえてしまえば怖くありません。今日学んだ設定を、ご自身のプロジェクトのコードでもぜひ見直してみてくださいね。一歩ずつ、安全なWebサービスを作っていきましょう!
コメント