こんにちは!ネットワークやAPIの世界へようこそ。インフラアーキテクトの私です。
日々のWeb開発やインフラ構築で、認証の仕組みとしてすっかりお馴染みになった「JWT(JSON Web Token)」。ログインしたユーザーに「あなたは本物の〇〇さんですよ」と証明するための、いわばデジタルな通行手形のようなものですね。
API設計の現場でも「ステートレスでスケーラブルな認証方式」として大人気ですが、このJWT、使い方を少しでも誤ると、裏口から泥棒を招き入れるような深刻なセキュリティホールになってしまうことをご存知でしょうか?
今回は、初学者やインフラに初めて触れる方にも分かりやすいように、身近な「郵便配達」の例えを交えながら、JWTの恐ろしい脆弱性である「noneアルゴリズム攻撃」と「鍵の推測(ブルートフォース攻撃)」、そしてその鉄壁の防御策について、一緒に紐解いていきましょう!
一歩ずつ、丁寧に解説していくので安心してくださいね。
—
1. そもそもJWTってなに?身近な例えで理解しよう
JWTの仕組みを難しく考える前に、私たちが普段使っている「手紙と封筒」に例えてみましょう。
あなたが友達に秘密の手紙を送るとき、こんな手順を踏みますよね。
1. 手紙を書く(Payload / ペイロード): 「ユーザーIDは123番、権限は一般会員です」という中身を書きます。
2. 封をしてスタンプを押す(Signature / 署名): あなたが持っている特別なハンコ(秘密の暗号鍵)を封筒の裏に押し、「これは間違いなく私(サーバー)が書いた本物の手紙です」と証明します。
受け取った側(サーバーやAPI)は、その手紙のスタンプを見て、「おっ、見覚えのある本物のハンコだ。中身は書き換えられていないな!」と安心して手紙を開けるわけです。この「中身(ペイロード)」と「本物であることを証明するスタンプ(署名)」がセットになったものが、まさにJWTの正体です。
—
2. 恐怖の「noneアルゴリズム攻撃」とは?
さて、ここからが本題です。この便利な手紙の仕組みに、とんでもない落とし穴(脆弱性)が潜んでいます。それが 「noneアルゴリズム攻撃」 です。
先ほどの郵便配達の例で考えてみてください。
悪意ある攻撃者が、あなたの手紙を途中でこっそり盗み見たとします。攻撃者は中身を勝手に書き換えました。「ユーザーIDは123番、権限は…よし、管理者(admin)に書き換えちゃおう!」と。
しかし、ここで問題が発生します。中身を書き換えたら、裏面の「本物のハンコ(署名)」が合わなくなってしまいますよね?サーバーに見破られてしまいます。
そこで攻撃者は、手紙の頭に書かれている「この手紙はこういう方法でハンコを押しましたよ」という仕様書(Header / ヘッダー)をこう書き換えたのです。
「今回の手紙、スタンプは一切なし(none)でお願いします!」
もし、サーバー側のプログラムがこの「スタンプなし(none)」という悪意ある指示をうっかり受け入れてしまったらどうなるでしょう?
サーバーは「あ、今回はスタンプ無しルールなんだね」と勘違いして、攻撃者が勝手に書き換えた偽物の手紙(管理者権限!)をすんなり本物として信用してしまうのです。これが、noneアルゴリズム攻撃の恐ろしい手口です。
なぜこれが起きるのか?
JWTのライブラリの古いバージョンなどでは、利便性(デバッグのしやすさなど)のために「署名検証を行わない(noneを許可する)」という設定がデフォルトで有効になっていることがありました。ここを攻撃者に突かれてしまうわけですね。
—
3. もう一つの脅威:弱い鍵の「ブルートフォース攻撃」
もう一つ注意しなければならないのが、スタンプを押すための「秘密の暗号鍵(シークレットキー)」の強度です。
例えば、あなたが家の鍵を「1234」という簡単な暗証番号にしていたら、空き巣にすぐに破られてしまいますよね。それと同じことがサーバーの裏側でも起きます。
開発者がテスト用や面倒くさいという理由で、秘密の鍵に secret や password123 のような、辞書に載っているような簡単な単語を設定してしまったとします。
攻撃者は、世界中にある何百万もの「よく使われるパスワードのリスト」を使い、ものすごいスピードで「この鍵かな?違う、この鍵かな?」と総当たりで試してきます(これがブルートフォース攻撃です)。
運悪く鍵が破られてしまうと、攻撃者はあなたと同じ「本物のハンコ」を手に入れたことになります。こうなると、もう誰も偽物の手紙を見抜けなくなってしまいます。
—
4. 実務で使える!堅牢なJWT実装と防御策
それでは、こうした脅威からシステムを守るために、私たちは一体どう設定すればよいのでしょうか?
ここからは、実際の開発やインフラ設定でそのまま使える具体的な防御策を見ていきましょう。
防御策①:noneアルゴリズムを徹底的に拒否する
ライブラリの設定で、none というアルゴリズムを明示的に禁止(ブラックリスト化)します。これによって、たとえ攻撃者が「スタンプなしで!」と要求しても、サーバー側で「そんなルールは認めません!」とピシャリと弾くことができます。
以下は、Pythonの有名なJWTライブラリ (PyJWT) を使った安全な検証のコード例です。
import jwt
# クライアントから受け取ったJWTトークンと、サーバーが持つ秘密の鍵
token = "eyJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoxMjN9."
secret_key = "super-secret-and-long-random-string-that-no-one-can-guess"
try:
# 【重要】 algorithms引数に "none" を絶対に含めず、許可するアルゴリズム(例: HS256)を明示する
# これにより、攻撃者がヘッダーを "alg": "none" に偽装してもエラーとして弾かれます
decoded_payload = jwt.decode(
token,
secret_key,
algorithms=["HS256"] # 安全なアルゴリズムだけを指定する
)
print("認証成功!中身:", decoded_payload)
except jwt.PyJWTError as e:
# 偽装されたトークンや、署名が不正な場合はここでキャッチされる
print("認証失敗:不正なトークンです。", e)
防御策②:十分な長さと複雑性を持つ鍵(シークレット)を使う
先ほどのコード例でも少し触れましたが、鍵の文字列があまりに短いと、あっという間に推測されてしまいます。
HMAC-SHA256(HS256)などの対称暗号を使う場合は、最低でも 256ビット(32バイト以上)のランダムな文字列 を使用するのがインフラ・セキュリティの鉄則です。
Linux環境であれば、以下のコマンドで簡単に推測されにくい強力なランダムキーを生成できます。
# OpenSSLを使って、URLエンコード安全な32バイトのランダム文字列を生成する
openssl rand -base64 32
(生成された出力例:K9s8V2xLp3Zq1wRt6Yu8Io0PaSdFgHjKlZxcVbnM=Þ)
こういった複雑な文字列を環境変数(.envファイルなど)に厳重に保管し、ソースコードに直接書き込まない(ハードコードしない)運用を徹底しましょう。
—
まとめ
今回は、JWTの代表的な脆弱性である「noneアルゴリズム攻撃」と「鍵の推測」について、郵便配達の例えを交えながら解説しました。
- JWTは「手紙(中身)」と「スタンプ(署名)」のセットである。
- 攻撃者は「スタンプなし(none)」を要求して偽の権限を通そうとするため、サーバー側で強制的にアルゴリズムを制限(ホワイトリスト方式)する必要がある。
- 「鍵(シークレット)」が推測されると全てが台無しになるため、十分な長さと複雑性を持たせ、環境変数で安全に管理する。
セキュリティの基本は「誰も安易に信用しないこと」です。APIを設計・構築する際は、便利なライブラリのデフォルト設定をうのみにせず、裏側でパケットやトークンがどのように検証されているのか、ぜひ意識を向けてみてくださいね。
それでは、また次回の深淵なネットワークの世界でお会いしましょう!
コメント