【入門編】 JWTの署名アルゴリズムをnoneに設定する攻撃手法と検証回避 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは。現場の最前線でシステムの「弱点」を見つけ出し、それを塞ぐアドバイスをしているセキュリティエンジニアです。

今日は、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の署名なし攻撃」は、仕組みを知ってしまえば「なーんだ、そんなことか」と思える内容だったかもしれません。でも、この「ちょっとした確認不足」が、大きな事故に繋がるのがセキュリティの世界の怖いところであり、面白いところでもあります。

まずは、自分の書いているコードや使っている設定で、「相手の言いなりにならず、こちらでルールを決めているか?」を意識してみてください。

一歩ずつ、安全なシステム作りを学んでいきましょう。もし不安なことがあれば、いつでも相談してくださいね。応援しています!

コメント

タイトルとURLをコピーしました