こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。
セキュリティの世界へようこそ!新人の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を絶対に許可しないこと。
セキュリティの対策は、一度覚えてしまえば難しいものではありません。「当たり前のことを、当たり前に厳しくチェックする」という基本の積み重ねが、あなたの大切なシステムとユーザーを守る盾になります。
一歩ずつ、確実にセキュアなコードを書けるエンジニアになっていきましょう!それでは、次回の記事もお楽しみに!
コメント