こんにちは!Webアプリの開発や、セキュリティの勉強を始めたばかりの新人エンジニアの皆さん、日々の業務本当にお疲れ様です。
「JWT(JSON Web Token)」って、ログイン機能やユーザー認証の仕組みを作るときによく耳にする言葉ですよね。「なんだか難しそうだな…」と感じていらっしゃる方も多いのではないでしょうか。
今日は、このJWTに潜むちょっと怖い「裏技」、そしてそれを防ぐための大切なコツについて、身近な「家の鍵」の防犯にたとえながら、一緒に優しく紐解いていきたいと思います。一歩ずつしっかり見ていきましょうね!
—
1. 家の鍵で考えてみよう!JWTの仕組みと「偽装」の罠
まずは、JWTが普段どんな役割をしているのかをイメージしてみましょう。
皆さんがお出かけするとき、家に入るには「鍵」が必要ですよね。最近はオートロックのマンションや、電子キーの家も増えています。
Webの世界でも同じで、ユーザーがログインに成功すると、サーバーはユーザーに対して「あなたは正しくログインした本人ですよ」という証明書(デジタル会員証のようなもの)を渡します。これが JWT です。
JWTの3つのパーツ
JWTは、ドット(.)で区切られた3つの部屋(パート)に分かれています。
1. Header(ヘッダー): 「この証明書は、こういうルール(アルゴリズム)で作られていますよ」という名札です。
2. Payload(ペイロード): 「ユーザーの名前は山田さんで、権限は一般ユーザーです」といった中身のデータです。
3. Signature(シグネチャー): ここが一番重要です!「サーバーだけが知っている秘密のハンコ(署名)」が押されています。
サーバーはこの「ハンコ」を確認することで、「おっ、この証明書はウチのサーバーが発行した本物だな!中身が途中で書き換えられていないぞ」と安心してユーザーを中に入れるわけです。
—
2. 攻撃者が狙う盲点!「アルゴリズム変更攻撃」とは?
さて、ここからが本題です。
悪意ある攻撃者は、「一般ユーザー」としてログインした後に、このJWTの証明書をこっそり書き換えて、「管理者権限(admin)」に成りすまそうと企らみます。
普通は、中身を「一般」から「管理者」に書き換えた瞬間に、サーバーの大切な「ハンコ(署名)」と一致しなくなるため、「おいおい、これ偽物だろ!」と弾かれます。当然ですよね。
しかし、ここにセキュリティの落とし穴(実装ミス)があると、攻撃者はいとも簡単にこのハンコをすり抜けてしまうのです。それが今回学ぶ「アルゴリズム変更攻撃」です。
罠その1:「ハンコいりません!」と言い張る攻撃(none攻撃)
ヘッダー部分に「今回の証明書、ハンコ(署名)の確認は不要(アルゴリズムを none に指定)にしてくださいね」と、勝手にルールを書き換えて送信する手口です。
もしサーバー側のプログラムが、「おっ、今回はハンコなしルールなんだな、OKOK!」とうっかり信じ込んでしまうと、攻撃者は中身を自由に書き換えてやりたい放題になってしまいます。
罠その2:「公開鍵」を「秘密鍵」だと勘違いさせる攻撃
少し高度な仕組み(非対称暗号方式:RSAなど)を使っている場合、サーバー側には「絶対に秘密にする鍵」があり、受け取る側には「誰にでも配る公開鍵」があります。
しかし、脆弱なライブラリや設定を使っていると、攻撃者が「ねえ、この公開鍵を秘密鍵として扱って、署名を検証してよ!」と意図的に仕向けたとき、サーバーがそれをそのまま信じ込んでしまうことがあります。
まるで、近所のコピー屋さんのスタンプを「これが我が家の公式ハンコです!」と言われて、うっかり通してしまうようなものです。恐ろしいですよね。
—
3. 実装コードで見る危険な罠と安全な対策
では、実際のプログラムではどうなっているのでしょうか。
Node.js(JavaScript)のjsonwebtokenライブラリを例に、危険なコードと安全なコードを比べてみましょう。
❌ やってはいけない!危険な実装例
以下のコードは、検証時のアルゴリズムを限定しておらず、受け取ったトークンの言うなりになってしまう危険な例です。
const jwt = require('jsonwebtoken');
// 危険な検証処理(アルゴリズムの制限をしていない)
function verifyTokenDanger(token, secretKey) {
try {
// トークンに "none" や想定外のアルゴリズムが指定されていても、そのまま通してしまう可能性アリ!
const decoded = jwt.verify(token, secretKey);
return decoded;
} catch (err) {
console.error("検証失敗:", err.message);
return null;
}
}
この書き方だと、攻撃者がヘッダーを細工して送り込んできたときに、すんなり通してしまう隙が生まれてしまいます。
⭕ こう書こう!安全な対策コード
それでは、しっかりとガードを固めた安全な書き方を見てみましょう。
ポイントは、「使用するアルゴリズムを明示的に指定(固定)する」ことです。
const jwt = require('jsonwebtoken');
// 安全な検証処理
function verifyTokenSafe(token, secretKey) {
try {
// 厳格に "HS256" だけを許可する! "none" や他のアルゴリズムはすべて拒否する
const options = {
algorithms: ['HS256'] // ここで使えるアルゴリズムをガチガチに固定します
};
const decoded = jwt.verify(token, secretKey, options);
return decoded;
} catch (err) {
// 偽装されたトークンや、不正なアルゴリズムが検知された場合はここで確実に弾かれます
console.error("不正なトークンを検知しました:", err.message);
return null;
}
}
このように、algorithms オプションを使って「我が社ではこのアルゴリズムしか認めません!」とあらかじめ厳しく宣言しておくことが、アルゴリズム変更攻撃を防ぐための最も確実で強力な防衛策になります。
—
4. 実務で今すぐ確認したいチェックリスト
日々の開発やインフラのメンテナンスにおいて、セキュリティ事故を防ぐために以下のポイントをチーム全体で確認してみてくださいね。
1. ライブラリのデフォルト設定を過信しない
- 使っているJWTライブラリが、デフォルトで
noneアルゴリズムを許可していないかドキュメントを確認しましょう。
2. 検証時には必ずアルゴリズムをホワイトリスト形式で指定する
- 先ほどのコード例のように、受け入れるアルゴリズムを明示的にコードに書き込みましょう(例:
['HS256']や['RS256']など)。
3. 最新のバージョンにアップデートする
- 古いバージョンのライブラリには、既知の脆弱性が残っていることがあります。定期的なアップデートを心がけましょう。
—
まとめ
いかがでしたでしょうか?
JWTのアルゴリズム変更攻撃は、仕組みを知ってしまえば「ちゃんと言いなりにならない設定をする」というシンプルで確実な対策で防ぐことができます。
「難しそう」と感じたセキュリティの知識も、身近な例えや正しいコードの書き方を知ることで、一歩ずつ自分のものにしていくことができます。
これからも一緒に、安全で堅牢なWebアプリケーションを作っていきましょう!応援しています!
コメント