【実務・中級編】JWTのペイロードにおける機密情報漏洩リスクと暗号化(JWE)の検討 – アプリケーションセキュリティ & 安全な開発防御ガイド

JWTの「署名=保護」という勘違いを正す ― 機密情報漏洩の盲点とJWEによる解決策

JWT(JSON Web Token)は今やWeb認証のデファクトスタンダードだ。しかし、現場のコードレビューで「これ、JWTの中にユーザーのメールアドレスや権限フラグをそのまま突っ込んでないか?」と指摘すると、決まって返ってくるのが「署名(Signature)があるから改ざんされないので安全ですよね?」という回答だ。

断言しよう。それは大きな誤解だ。

JWTの署名は「改ざん検知」のためのものであって、「秘匿(暗号化)」のためのものではない。Base64Urlでデコードすれば、誰でもペイロードの中身を覗ける。今日は、JWTの構造的な弱点を突き、我々がどう立ち回るべきかを徹底解説する。

1. なぜ「署名」だけでは不十分なのか?

JWTは `Header.Payload.Signature` の3部構成だ。

  • Header/Payload: Base64Urlエンコードされているだけ。
  • Signature: ヘッダーとペイロードを秘密鍵でハッシュ化したもの。

ここで重要なのは、「Base64Urlエンコードは暗号化ではない」という点だ。攻撃者がJWTを入手すれば、ブラウザのコンソールやオンラインデコーダーに貼り付けるだけで、ペイロード内の「氏名」「メールアドレス」「内部的なユーザーID」「権限ロール」が丸裸になる。

攻撃者の視点:PoC(概念実証)の恐怖

攻撃者は以下のようなステップで機密情報を搾取する。
1. 中間者攻撃(MitM): Wi-Fi盗聴やプロキシ経由でトークンを奪取。
2. デコード: `atob()` や `jwt.io` で中身を確認。
3. 特権昇格の試行: ペイロード内に `{“role”: “user”}` とあれば、これを `{“role”: “admin”}` に書き換え、アルゴリズムを `none` に設定して改ざんを試みる(脆弱なライブラリを使っている場合、これが通る)。

2. 設計の原則:JWTに機密情報は載せない

大原則はシンプルだ。「JWTには『識別子(sub/jti)』以外は含めない」

メールアドレスや詳細なプロフィール情報は、JWTに入れるのではなく、トークンに含まれる `user_id` をキーとしてバックエンドのDBやRedisから都度取得するのが正しいアーキテクチャだ。

しかし、「どうしても通信量を減らしたい」「オフラインで情報を検証したい」という要件がある場合はどうするか? そこで登場するのが JWE (JSON Web Encryption) だ。

3. JWEによる暗号化実装サンプル

JWTを暗号化してJWE(JSON Web Encryption)にすることで、ペイロードの内容は「鍵を知る者」以外には解読不能な文字列になる。

Node.js (joseライブラリ) での実装例

実務で最も信頼性が高い `jose` ライブラリを使用した例だ。

import { EncryptJWT, generateKeyPair } from ‘jose’;

// 1. 公開鍵・秘密鍵の生成(本来は環境変数やKMSから取得)
const { publicKey, privateKey } = await generateKeyPair(‘RSA-OAEP-256’);

// 2. 暗号化されたJWT(JWE)の作成
const jwe = await new EncryptJWT({ ‘urn:example:claim’: ‘機密情報’ })
.setProtectedHeader({ alg: ‘RSA-OAEP-256’, enc: ‘A256GCM’ })
.setIssuedAt()
.setIssuer(‘urn:example:issuer’)
.setExpirationTime(‘2h’)
.encrypt(publicKey); // 公開鍵で暗号化

console.log(‘生成されたJWEトークン:’, jwe);

このトークンは、`privateKey` を持つサーバー側でしか復号できないため、途中で盗聴されてもペイロードの中身は絶対に漏れない。

4. 運用現場で死なないためのチェックリスト

実装だけで満足してはいけない。インフラと設計の両面で守りを固めろ。

① WAFによる不正なJWTの遮断

AWS WAFやNginx等のフロントエンドで、トークンの構造をチェックする。

Nginxで Authorization ヘッダーの不審なJWTパターンを弾く(簡易的な正規表現)
署名部分が極端に短いものや、noneアルゴリズムを意図する文字列を監視
if ($http_authorization ~ “none”) {
return 403;
}

② 鍵管理の鉄則(KMSの利用)

ハードコーディングされた鍵は即座に漏洩のリスクがある。AWS KMSやGoogle Cloud KMS を使い、暗号化・復号の処理自体をクラウドのセキュリティモジュールにオフロードせよ。鍵のローテーション(定期的な変更)も自動化できる。

③ 「機密情報」の定義をチームで共有する

開発チームのSlackやWikiに以下のルールを明記せよ。

  • JWTに載せて良いもの: `sub` (ユーザーID), `exp` (期限), `iat` (発行日), `jti` (一意なID)
  • JWTに絶対載せてはいけないもの: `email`, `password_hash`, `credit_card`, `internal_role` (権限名そのもの), `pii` (個人を特定できる情報)

最後に:セキュリティは「性悪説」で考えろ

「今の実装ならバレないだろう」「このデータなら載せてもいいか」という甘えが、後に数百万件の個人情報漏洩を引き起こす。JWTは非常に強力だが、その仕組みを深く理解していないエンジニアにとっては諸刃の剣だ。

今日からあなたのプロジェクトのJWTを見直してほしい。もし設計に不安があるなら、まずはJWEを検討するか、あるいは「トークンの中身を空にする(IDだけにする)」リファクタリングから始めてみよう。

それが、あなたのサービスを守る最初の一歩になる。

コメント

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