こんにちは。現場の最前線でセキュリティと格闘しているエンジニアの皆さん、お疲れ様です。
今日は、Webアプリケーションの「身分証明書」として広く使われているJWT(JSON Web Token)の、ちょっと怖くて面白い脆弱性についてお話しします。
「JWT? なんか難しそう…」と思ったあなたも大丈夫。まずは身近な例から、一緒に紐解いていきましょう。
—
1. JWTは「改ざん防止機能付きの身分証」
JWTは、サーバーが発行する「あなたは〇〇さんですね」というデジタルな身分証です。これを見せれば、毎回パスワードを入力しなくてもサイトを利用できますよね。
この身分証には、大きく分けて3つのパーツがあります。
1. ヘッダー(どんな形式の身分証か)
2. ペイロード(名前や権限などの情報)
3. 署名(シグネチャー)(これが一番大事!「サーバーが本物だと認めた」というハンコ)
この「署名」があるおかげで、もし泥棒が勝手に情報を書き換えても、サーバーは「あれ? このハンコ、うちのじゃないな」とすぐに見抜くことができる仕組みになっています。
2. 泥棒が狙う「署名アルゴリズム ‘none’」の罠
さて、ここで悪意ある泥棒(攻撃者)が考えます。
「このハンコがなければ、中身を書き換えてもバレないんじゃないか?」と。
そこで登場するのが、署名アルゴリズム none という脆弱性です。
JWTのヘッダー部分には、署名にどのアルゴリズムを使っているかが書かれています。普通は HS256(共通鍵)や RS256(公開鍵)などが使われますが、これをnone(なし)に書き換えてしまうのです。
どうやって攻撃するの?
1. 泥棒は、自分のログイン情報をJWTで入手します。
2. そのJWTのヘッダーにある "alg": "HS256" という部分を "alg": "none" に書き換えます。
3. さらに、ペイロードのユーザーIDを管理者IDに書き換えます。
4. 最後に、署名部分を空っぽ(削除)にします。
ライブラリの設定が甘いと、サーバーは「お、この身分証は『署名なし』でいい設定だな。じゃあ中身を確認しなくていいや」と信じ込んでしまい、まんまと管理者としてログインできてしまうのです。これが「認証回避」の恐ろしい実態です。
3. どう守ればいい? 対策の基本ステップ
「署名なんていらない」と主張する偽の身分証を、門前払いする仕組みを作りましょう。
① デフォルト設定を信じない
多くのJWTライブラリは、便利な反面、柔軟すぎることがあります。「none アルゴリズムを許可しない」という明示的な設定が必要です。
② 厳格な検証を実装する
以下のように、受け取るアルゴリズムを厳格に制限しましょう。
// 良い例:検証時にアルゴリズムを固定する
const jwt = require(‘jsonwebtoken’);
// サーバー側で検証する際、必ずアルゴリズムを明示する
jwt.verify(token, publicKey, { algorithms: [‘RS256’] }, (err, decoded) => {
if (err) {
// 署名が合わない、あるいはアルゴリズムが異なればエラーにする
console.error(“不正な身分証です!”);
return;
}
// 正常な場合
console.log(“認証成功:”, decoded);
});
③ 公開鍵暗号(RS256)の推奨
共通鍵(HS256)は、サーバーとクライアント(または複数のサーバー)で鍵を共有するため、鍵が漏洩するリスクが高いです。RS256(公開鍵方式)を使えば、サーバーは「秘密鍵」で署名し、検証する側は「公開鍵」でチェックするだけで良いため、より安全に運用できます。
—
セキュリティは「疑うこと」から始まる
駆け出しの頃、私は「ライブラリがよしなにやってくれるはず」と信じ切っていました。しかし、セキュリティの現場では「ライブラリのデフォルト設定ほど、攻撃者に研究し尽くされているものはない」というのが教訓です。
今回紹介したJWTの脆弱性は、ほんの一例です。
- 「本当にこの設定で大丈夫か?」
- 「もし攻撃者がヘッダーを書き換えたらどうなる?」
そんな「疑いの目」を持つことが、あなたの書くコードを、そしてユーザーのデータを守る最強の武器になります。
一歩ずつで構いません。まずは今使っているライブラリのドキュメントを開いて、「署名の検証設定」がどうなっているか確認することから始めてみませんか?
皆さんの開発が、よりセキュアで、自信に満ちたものになることを応援しています!
コメント