こんにちは!日々の開発やインフラの保守、本当にお疲れ様です。新人のIT担当者さんや、「セキュリティってなんだか難しそう……」と不安を感じている一般開発者の方に向けて、今日から現場で役立つセキュリティの知識を優しく、そして徹底的に深掘りしてお届けします。
さて、皆さんはWebアプリケーションのログイン状態を維持したり、安全にデータをやり取りしたりするために、JWT(JSON Web Token)という言葉を耳にしたことはありませんでしょうか? 「名前は聞いたことがある」「なんだか複雑な文字列だよね」と思ったそこのあなた、大正解です。このJWT、現代のWeb開発ではなくてはならない便利な仕組みなのですが、使い方を一つ間違えると、システム全体のセキュリティがガラガラと崩れ去る危険な一面も持っています。
今回は、数あるJWTの脆弱性の中でも、攻撃者がこっそりと悪用する「alg: none脆弱性」と「鍵の混同攻撃(Key Confusion Attack)」の2つに焦点を当てます。小難しい専門用語も、身近な例えを交えながら一歩ずつ紐解いていきますので、ぜひ最後までリラックスして読んでいってくださいね!
—
そもそもJWTってどんな仕組み?(身近な例えで理解する)
まずは、JWTがどんなものなのかをイメージしてみましょう。
Webの世界では、「私は〇〇という権限を持った、佐藤というユーザーです」という証明を、サーバーとブラウザの間で何度もやり取りする必要があります。
ここで毎回、データベースに「佐藤さんは管理者ですか?」と問い合わせていたら、サーバーがパンクしてしまいますよね。そこで登場するのがJWTです。
これは、いわば「サーバーが発行する特別な証明書(入場パス)」のようなものです。
例えば、遊園地を想像してください。チケット売り場(認証サーバー)で入場料を払うと、「このチケットは本物です。アトラクションの優先レーンに入れます」というスタンプが押されたパスポート(JWT)が手に入ります。アトラクションの係員(APIサーバー)は、そのパスポートのスタンプを確認するだけで、チケット売り場に確認しなくてもあなたを中に入れますよね。
このJWTは、主に以下の3つのパーツがドット(.)で繋がった構造をしています。
1. Header(ヘッダー): 「このパスポートはどんなルール(アルゴリズム)で封印されているか」が書かれています。
2. Payload(ペイロード): 「誰が、どんな権限を持っているか(ユーザーIDや有効期限など)」という中身です。
3. Signature(署名): 「このパスポートは、チケット売り場が本気で作ったものであり、途中で悪意ある人に書き換えられていない」ことを証明する改ざん防止のスタンプ(暗号的な署名)です。
この3つの中で一番重要なのが、3つ目の「Signature(署名)」です。これがなければ、誰でも勝手にパスポートのペイロード(中身)を「私は管理者です!」と書き換えて入場できてしまいますよね。
—
恐怖の仕組みその1:alg: none 脆弱性(「偽物のスタンプ」を見破れない大失態)
さて、ここからが本題です。攻撃者は、この便利なパスポート(JWT)をどうやって偽造しようと企むでしょうか?
攻撃のメカニズム:スタンプを勝手に「なし」にする
先ほど、ヘッダーには「どんなルールで封印されているか」が書かれていると言いました。ここに、悪意あるハッカーが目を付けました。
通常、署名を作るためには「秘密の合言葉(秘密鍵)」が必要です。チケット売り場だけがその合言葉を知っていて、その合言葉を使ってスタンプを押します。係員も同じ合言葉を知っているから、スタンプが本物だと分かります。
しかし、もしヘッダーに「このチケット、署名(スタンプ)は不要(alg: none)ですよ」と書いてあったらどうでしょう?
お人好しなAPIサーバー(プログラム)が、「おっ、このパスポートは署名がいらないルールなんだな。じゃあ中身をチェックしなくていいや!」と受け入れてしまったら……。
ハッカーは、ペイロードの部分を「管理者権限(admin: true)」に書き換え、ヘッダーに {"alg": "none"} と書いてサーバーに送りつけるだけで、いとも簡単に最高権限を奪い取ることに成功してしまうのです。
これが、alg: none 脆弱性の恐ろしい正体です。現実世界に例えるなら、泥棒が自分で「この財布の中身は警察が確認済みです」という真っ白なメモを貼って、交番の前を堂々と歩いているようなものですね。
—
恐怖の仕組みその2:鍵の混同攻撃(Key Confusion Attack)
もう一つの恐ろしい手口が「鍵の混同攻撃(Key Confusion Attack)」です。こちらはもう少し巧妙です。
暗号の世界には、大きく分けて2つのやり方があります。
1. 共通鍵暗号(例: HMAC / HS256): サーバー側だけが知っている「秘密の合言葉」を、署名を作る時と確認する時の両方で使う方法。(家族の合言葉のようなもの)
2. 公開鍵暗号(例: RSA / RS256): 署名を作る時は「秘密鍵」を使い、確認する時は誰にでも配る「公開鍵」を使う方法。(鍵付きのポストと、そのポストを開けられない鍵のようなもの)
なぜ混同してしまうのか?
通常、RSA(公開鍵暗号)を使うシステムでは、公開鍵を使って「この署名は正しいか?」を確認します。
しかし、脆弱なライブラリや実装ミスがあるシステムでは、「公開鍵を、うっかり共通鍵(HMAC)の合言葉として勘違いしてしまう」というバグが起きます。
攻撃者は、次のような悪だくみをします。
1. システムが持っている「公開鍵(誰でも手に入るもの)」を手に入れます。
2. 攻撃者は、その「公開鍵」を自分の手元にある秘密の合言葉(共通鍵)に見立てて、自分で自由にJWTの署名(HMAC)を作ります。
3. サーバーにそのJWTを送ると、サーバーは「おっ、うちの公開鍵(合言葉)で正しく署名されているな!」と勘違いして、改ざんされたトークンを本物として受け入れてしまうのです。
鍵の役割がごちゃ混ぜになってしまうことから、「鍵の混同(Key Confusion)」と呼ばれるゆえんです。
—
一歩ずつ対策を学んでいきましょう!【実務で使える防御策】
「うわ、なんだか怖くなってきた……私の作ったシステム、大丈夫かな?」と不安になったあなた、安心してください。セキュリティは、正しい知識を持って適切な設定を行えば、しっかりと守ることができます。
ここからは、実務の現場で今日から使える具体的な対策を、コードや設定例を交えて解説していきますね!
対策1:alg: none を絶対に受け入れない(またはライブラリのデフォルトを過信しない)
大前提として、「署名なし(none)」のJWTをサーバーが処理しては絶対にダメです。多くのモダンなJWTライブラリでは、デフォルトで none を弾いてくれますが、古いライブラリや独自の検証ロジックを書いている場合は要注意です。
検証を行う際は、必ず「受け入れるアルゴリズムを明示的に指定(ホワイトリスト方式)」しましょう。
以下は、Node.jsの有名なライブラリ jsonwebtoken を使った、安全な検証のコード例です。
const jwt = require('jsonwebtoken');
// サーバーが保持している安全な秘密鍵
const SECRET_KEY = 'your-super-secret-and-safe-key';
function verifyUserToken(token) {
try {
// 【重要】algorithmsオプションで、許可するアルゴリズムを「RS256」や「HS256」などに明示的に限定する!
// ここに 'none' を含めてはいけません。
const decoded = jwt.verify(token, SECRET_KEY, { algorithms: ['HS256'] });
console.log('トークンの検証に成功しました:', decoded);
return decoded;
} catch (err) {
console.error('不正なトークンです:', err.message);
return null;
}
}
このように、algorithms: ['HS256'] と明示的に指定することで、仮に攻撃者が alg: none と書き換えたJWTを送ってきたとしても、ライブラリ側が「そんなアルゴリズムは許可されていません!」と自動的に弾いてくれます。
対策2:アルゴリズムの自動判別(型チェックの罠)を避ける
鍵の混同攻撃を防ぐためには、JWTのヘッダーにある alg の値を鵜呑みにして、内部の検証処理を動的に切り替える実装を避けることが極めて重要です。
例えば、「ヘッダーが HS256 なら共通鍵で検証し、RS256 なら公開鍵で検証する」というコードを自前で書こうとすると、攻撃者にヘッダーを書き換えられた際に思わぬ脆弱性を生む原因になります。
サーバー側が「このエンドポイントでは、このアルゴリズムしか使わない」とあらかじめ固定のルールを持たせておくことが、泥棒の侵入を防ぐ堅牢な扉となります。
—
まとめ
今回は、JWTの恐ろしい脆弱性である alg: none と鍵の混同攻撃のメカニズム、そしてその具体的な対策についてお話ししました。
alg: none攻撃: 「スタンプ(署名)はいりません」という嘘を、お人好しなプログラムに見破らせずに侵入する手口。許可するアルゴリズムを明示的に絞ることで防ぐ。- 鍵の混同攻撃: 公開鍵と共通鍵の役割の勘違いを利用する手口。アルゴリズムの自動判別に頼らず、厳格な検証ロジックを組むことで防ぐ。
セキュリティ対策は、一度覚えたら終わりではなく、日々の開発の中で「本当にこの設定で大丈夫かな?」と疑い、確認する姿勢が何よりも大切です。ぜひ、ご自身のプロジェクトのコードやライブラリの設定を見直してみてくださいね。
それでは、次回のセキュリティ解説もお楽しみに!安全で快適な開発ライフを送りましょう!
コメント