こんにちは。現場の最前線でシステムの「弱点」を見つけ出し、それを塞ぐアドバイスをしているセキュリティエンジニアです。
今日は、Webアプリの開発で非常によく使われるJWT(JSON Web Token)という技術に潜む、ちょっと驚きの「落とし穴」についてお話しします。
「JWT? 難しそうだな……」と思うかもしれませんが、大丈夫です。今回は、私たちの身近な「家の鍵」や「会員証」に例えて、ITの専門知識がなくてもイメージできるように優しく紐解いていきますね。
これからお話しするのは、「署名アルゴリズムを none に書き換える」という、泥棒が使う手口のような攻撃手法です。一緒に一歩ずつ対策を学んでいきましょう!
—
1. JWTは「改ざんできないデジタル会員証」のはずだった
まず、JWTがどんなものか簡単にイメージしてみましょう。
JWTは、サーバーから発行される「デジタル会員証」のようなものです。
この会員証には、以下の3つの情報がセットになっています。
1. ヘッダー(表紙):「これはどんな種類のカードか?」
2. ペイロード(中身):「誰が持っているか?(ユーザー名など)」
3. シグネチャ(署名):「店長が書いた本物のサイン」
お店(サーバー)は、あなたが提示したカードの「サイン」を見て、「よし、これは私が書いた本物だ。名前も書き換えられていないな」と確認します。これが「署名検証」です。
—
2. 泥棒の手口: 「サインはいらないよ」と嘘をつく
さて、ここで悪い泥棒(攻撃者)が登場します。
泥棒は、他人の会員証を盗むか、自分で適当なカードを作ります。そして、カードの「中身」を自分の都合のいいように書き換えます。例えば、「一般会員」を「管理者(社長)」に書き換えるわけです。
普通なら、中身を書き換えると「店長のサイン(署名)」と矛盾が出るので、お店でバレてしまいます。そこで泥棒は、「ヘッダー(表紙)」に細工をします。
「alg: none」という魔法の言葉
泥棒は、カードのヘッダーにある「署名方法(alg)」という項目を、none(なし)に書き換えてしまいます。
- 本来: 「このカードは、店長の特殊なペン(HS256など)でサインされています」
- 書き換え後: 「このカードは、サイン不要の特別なカードです」
もし、お店のスタッフ(サーバーのプログラム)が「あ、表紙に『サイン不要』って書いてあるから、サインのチェックはしなくていいんだな」と鵜呑みにしてしまったら……?
泥棒はサインなしで、堂々と「管理者」としてお店に入ることができてしまいます。これが「JWTの none アルゴリズム攻撃」の正体です。
—
3. なぜこんなことが起きてしまうの?
「そんなバカなチェック漏れがあるの?」と思うかもしれません。
しかし、これは過去に多くの有名なシステムで見つかった脆弱性なんです。原因は大きく分けて2つあります。
1. ライブラリの古い仕様: 昔のJWTを扱うプログラム(ライブラリ)の中には、デバッグ(開発中のテスト)を楽にするために、あえて none を受け入れてしまうものがありました。
2. 「何でも受け入れる」設定: プログラムが「どんな署名方法でも、書かれている通りに処理するよ」という受け身な姿勢だったため、泥棒の嘘に騙されてしまったのです。
—
4. プロの視点: レッドチームが狙う「盲点」
私たちのような攻撃側の視点(レッドチーム)から見ると、開発者が「有名なライブラリを使っているから大丈夫だろう」と安心しきっている場所こそが、絶好の狙い目になります。
特に、「署名があるかどうか」を確認する前に、「どの署名方法を使っているか」を先に読み取ってしまう実装は、この攻撃に対して非常に脆いです。
—
5. 【防御策】泥棒をシャットアウトする「鉄壁の設定」
では、どうすればこの攻撃を防げるのでしょうか?
答えは簡単です。「うちはこのサイン(署名方法)しか認めない!」とお店のルールを厳格に決めておくことです。
以下に、Node.jsでよく使われるライブラリ jsonwebtoken を使った、安全な実装例を紹介します。
安全な検証コードの書き方
const jwt = require('jsonwebtoken');
// サーバーだけが知っている秘密の鍵
const SECRET_KEY = "your-very-secure-secret-key";
// クライアントから送られてきたトークン(例)
const token = "ここに送られてきたJWTが入ります";
try {
// 【超重要!】第3引数で、許可するアルゴリズムを明示的に指定します
// これにより、たとえ攻撃者が 'alg': 'none' と送ってきても、エラーとして弾けます
const decoded = jwt.verify(token, SECRET_KEY, {
algorithms: ['HS256'] // HS256という方式以外は一切認めない!
});
console.log("認証成功! ユーザー名:", decoded.username);
} catch (err) {
// 署名が合わない場合や、'none' が送られてきた場合はここに来ます
console.error("認証に失敗しました。不正な操作の可能性があります!", err.message);
}
対策のポイント
- アルゴリズムの固定:
algorithms: ['HS256']のように、使用する方式をホワイトリスト(許可リスト)で指定しましょう。 - ライブラリの更新: 古いライブラリには
noneを許可してしまうバグがあるかもしれません。常に最新版を使いましょう。 - 秘密鍵の管理: サインに使う「ペン(秘密鍵)」自体が盗まれたら元も子もありません。環境変数などで厳重に管理してくださいね。
—
最後に: セキュリティは「一歩ずつ」の積み重ね
いかがでしたでしょうか?
「JWTの署名なし攻撃」は、仕組みを知ってしまえば「なーんだ、そんなことか」と思える内容だったかもしれません。でも、この「ちょっとした確認不足」が、大きな事故に繋がるのがセキュリティの世界の怖いところであり、面白いところでもあります。
まずは、自分の書いているコードや使っている設定で、「相手の言いなりにならず、こちらでルールを決めているか?」を意識してみてください。
一歩ずつ、安全なシステム作りを学んでいきましょう。もし不安なことがあれば、いつでも相談してくださいね。応援しています!
コメント