【入門編】 JWTの署名検証不備(Noneアルゴリズム)の悪用 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。
セキュリティの世界へようこそ!新人のIT担当者さんや、「セキュリティってなんだか難しそうだな…」と感じている一般開発者の方に向けて、今日から現場で役立つ知識を優しく、そして泥臭くお届けしていきますね。

さて、今回のテーマは「JWTの署名検証不備(Noneアルゴリズム)の悪用」です。
名前を聞くだけで「うっ、難しそう…」と身構えてしまうかもしれませんが、大丈夫です!身近な「合鍵」の防犯にたとえながら、一歩ずつ一緒に紐解いていきましょう。

—

1. 家の鍵で例える「JWT」と「電子署名」の仕組み

皆さんは、家を出るときに鍵をかけますよね?
そして、その鍵には「ピッキングされにくい頑丈なもの」や「本人しか持てないデジタルキー」など、いろいろな工夫があります。

Webの世界でも同じです。ユーザーがログインしたあと、「私は〇〇会社の山田です!」と証明するために使われるのが JWT(JSON Web Token) という仕組みです。

JWTってそもそもなに?

JWTは、ユーザーの情報(名前や権限など)をコンパクトにまとめた「身分証明書」のようなものです。この身分証明書は、大きく分けて3つのパーツでできています。

1. ヘッダー(Header): 「どんな暗号の仕組み(アルゴリズム)を使っているか」を書く場所
2. ペイロード(Payload): 「誰がログインしているか(ユーザー名や権限)」を書く場所
3. 署名(Signature): 「この身分証明書は、本物のサーバーが発行しましたよ」という改ざん防止のハンコ

この中で一番大切なのが、3つ目の「署名(ハンコ)」です。
サーバーだけが知っている秘密の合言葉を使ってハンコを押すため、悪い人が勝手に中身を書き換えても、ハンコがない(あるいは偽物)ので、サーバーに「これ偽物だね!」とすぐバレる仕組みになっています。

—

2. 泥棒の手口:ハンコが不要になる「Noneアルゴリズム」の罠

ここで、今回の本題である「Noneアルゴリズムの悪用」という攻撃の登場です。

現実世界で考えてみてください。
最高級の防犯ロックがついた頑丈なドアがあったとします。泥棒は普通、鍵をピッキングしたり、窓ガラスを割ったりして侵入しようと試みますよね。

しかし、もしそのドアに「今日は鍵を開けっ放しにしておきますね。誰でも自由に入っていいですよ」という看板(設定ミス)が掲げられていたらどうでしょうか?
泥棒はピッキングすらする必要がありません。ただ堂々と玄関から入って、中の財宝を総取りできますよね。

JWTの「Noneアルゴリズム」の脆弱性とは、まさにこれと同じ状態を指します。

攻撃のメカニズム

攻撃者は、ログインしたときにブラウザにもらうJWTの身分証明書を次のようにこっそり書き換えます。

1. ペイロード(中身)を書き換える

  • 「私は一般社員です」というデータを、「私は最高管理者(Administrator)です」に書き換えます。

2. ヘッダーのアルゴリズムを none に書き換える

  • ヘッダー部分を「今回は署名(ハンコ)の確認をしなくていいです("alg": "none")」という指定に変更します。

3. 署名(ハンコ)を消し去る

  • ハンコの検証をしなくてよくなったため、署名のパーツをごっそり削除してサーバーに送信します。

もし、サーバー側のプログラムが「おっ、ヘッダーに none って書いてあるから、今回はハンコのチェックをパスしよう!」とうっかり受け入れてしまうと、攻撃者はパスワードも知らずに「最高管理者」としてシステムに入り込むことに成功してしまうのです。これがこの脆弱性の恐ろしいところです。

—

3. 現場でありがちな危険なコードと脆弱性の再現

では、実際の開発現場で、なぜこのような事故が起きてしまうのでしょうか?
例えば、Node.js(JavaScript)を使ったバックエンドのコードで、次のような実装をしてしまったと仮定しましょう。

以下のコードは、セキュリティ上非常に危険なアンチパターンです。

const jwt =  require('jsonwebtoken');

// 【危険な実装例】アルゴリズムの制限を緩くしてしまっているコード
function verifyTokenDangerously(token) {
    try {
        // サーバーの秘密鍵を使って検証しようとしているが...
        // もしトークン側で "alg": "none" が指定された場合、
        // 設定によっては検証がスルーされてしまうことがあります。
        const secretKey = 'my_super_secret_key';
        
        // optionsにアルゴリズムの指定がない、または不適切な場合が狙われる
        const decoded = jwt.verify(token, secretKey);
        
        return decoded; // 検証が通ってしまうと、改ざんされたペイロードが返される
    } catch (err) {
        console.log("認証エラー: ", err.message);
        return null;
    }
}

このコードの何が問題か分かりますか?
ライブラリのデフォルト挙動や設定の不備により、攻撃者から送り込まれた alg: none のトークンを「有効なもの」として誤認してしまう余地を与えてしまっている点です。

—

4. 一歩ずつ学ぶ!堅牢な防御と対策の実装

「うわっ、怖くなったな…うちのシステムは大丈夫かな?」と心配になった方もご安心ください!
今からしっかり対策を覚えて、明日からの開発に活かしましょう。

防犯対策の基本は「怪しいルールを一切受け入れないこと」です。

対策1: アルゴリズムを厳格に固定する

ライブラリを使うときは、「我が社(我がアプリ)では、この暗号方式(例えば HS256 や RS256 など)しか使わない!」と明示的に指定(ホワイトリスト化)しましょう。none などの未知のアルゴリズムが指定された場合は、問答無用でエラーにするのが鉄則です。

先ほどのNode.jsのコードを、安全な形に修正してみましょう。

const jwt = require('jsonwebtoken');

// 【安全な実装例】使用するアルゴリズムを厳格に限定する
function verifyTokenSafely(token) {
    try {
        const secretKey = 'my_super_secret_key';
        
        // optionsの algorithms に使用を許可するアルゴリズムの配列を必ず指定する!
        // これにより、攻撃者が "alg": "none" を送り込んできても、拒否されるようになります。
        const options = {
            algorithms: ['HS256'] // HS256 以外のアルゴリズムはすべて拒否!
        };
        
        const decoded = jwt.verify(token, secretKey, options);
        
        console.log("認証成功!ユーザー権限: ", decoded.role);
        return decoded;
        
    } catch (err) {
        // alg: none や改ざんされたトークンは、ここでしっかりと弾かれます
        console.error("セキュリティ警告: 不正なトークンが検知されました ->", err.message);
        return null;
    }
}

対策2: 信頼できる枯れたライブラリを選ぶ

JWTの検証処理を自前で実装しようとするのは、自家製の鍵を作ろうとするようなもので、非常に危険です。世界中のセキュリティエンジニアによってテストされ、メンテナンスされている実績のあるライブラリ(Pythonなら PyJWT、Javaなら JJWT、Node.jsなら jsonwebtoken など)を正しく設定して使いましょう。

—

5. まとめ

今回は、JWTの署名検証不備(Noneアルゴリズム)の仕組みと、その防ぎ方についてお話ししました。

  • 攻撃の要点: ヘッダーの alg を none に書き換えることで、サーバーの署名チェックをすり抜けようとする手口。
  • 防御の要点: サーバー側で受け入れるアルゴリズムを明示的にホワイトリスト形式(['HS256'] など)で固定し、none を絶対に許可しないこと。

セキュリティの対策は、一度覚えてしまえば難しいものではありません。「当たり前のことを、当たり前に厳しくチェックする」という基本の積み重ねが、あなたの大切なシステムとユーザーを守る盾になります。

一歩ずつ、確実にセキュアなコードを書けるエンジニアになっていきましょう!それでは、次回の記事もお楽しみに!

コメント

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