JWTの脆弱性はなぜ「扉の鍵」ではなく「偽造パスポート」になるのか?—noneアルゴリズムと鍵推測への防衛戦略
ネットワークエンジニアとして数々のAPIゲートウェイのログを追ってきた経験から言わせてもらうと、JWT(JSON Web Token)は「便利だが扱いを間違えると一瞬で屋台骨が崩れる」劇薬です。
REST APIの設計において、リソースの特定とステートレスな認証を両立させるためにJWTはデファクトスタンダードとなりました。しかし、その中身を理解せずに「とりあえずライブラリを通した」実装をしている現場は、あまりにも脆い。今回は、JWTが抱える二大脆弱性である「noneアルゴリズム攻撃」と「鍵の推測」について、現場の視点から深掘りします。
—
1. JWTの構造と、なぜ「none」が危険なのか
JWTは Header.Payload.Signature の3つのパートで構成されています。ここで重要なのは、Header に含まれる alg (Algorithm) フィールドです。
本来、サーバーは署名を検証する際、決め打ちのアルゴリズム(例えば HS256)で検証を行うべきです。しかし、一部のライブラリや実装では、JWTのヘッダーに書かれた alg の値を信頼して検証アルゴリズムを動的に切り替えてしまうという致命的な実装ミスを犯すことがあります。
攻撃の手口:none アルゴリズムによる署名バイパス
攻撃者は以下のようなヘッダーを細工してサーバーに送りつけます。
{
"alg": "none",
"typ": "JWT"
}
もしサーバー側のバリデーターが「alg が none なら署名チェックをスキップする」というロジックを持っていれば、攻撃者は Payload(例:{"user_id": "admin"})を自由に書き換えて、署名なしでAPIを叩くことが可能になります。これは、鍵の有無を確認するゲートを通らずに、偽造された身分証だけで入館するようなものです。
—
2. 鍵推測(ブルートフォース)攻撃の現実
HS256 (HMAC SHA-256) のような共通鍵暗号方式を使っている場合、鍵(Secret)自体が弱いと、オフライン攻撃で突破されます。
攻撃者は、盗み出したJWTのヘッダーとペイロードを保持したまま、辞書攻撃やブルートフォースで鍵を総当たりします。Pythonの hashlib を使えば、数百万通りの鍵を高速に試すことは容易です。
防衛策:鍵の複雑性とローテーション
「mysecret」のような単純なパスワードを鍵にするのは、鍵をかけずに玄関を開けておくのと同義です。
- 鍵の長さ: 256ビット(32バイト)以上のランダムな文字列を使用すること。
- 環境変数管理: 鍵をコードに直書きせず、
AWS Secrets ManagerやHashiCorp Vaultで厳格に管理する。
—
3. 実践:安全なJWT検証のためのコード実装(Python)
脆弱性を防ぐための鍵は、「ライブラリのデフォルトを信用せず、明示的に検証アルゴリズムを固定する」ことにあります。
import jwt
# 良い例: 使用するアルゴリズムを明示的にホワイトリスト化する
def verify_token(token, secret_key):
try:
# algorithms引数で許容するアルゴリズムを固定。
# ここに 'none' を含めてはいけない。
decoded = jwt.decode(
token,
secret_key,
algorithms=["HS256"]
)
return decoded
except jwt.InvalidAlgorithmError:
# 意図しないアルゴリズムが指定された場合は即座に拒否
print("警告: 不正なアルゴリズムが検出されました")
except jwt.ExpiredSignatureError:
print("トークンの有効期限が切れています")
except Exception as e:
print(f"検証エラー: {e}")
—
4. 運用現場で押さえておくべきチェックリスト
API設計者、あるいはインフラエンジニアとして、JWTを扱う際は以下のポイントを必ず確認してください。
1. alg フィールドの検証: jwt.decode する際は、必ず algorithms 引数で期待するアルゴリズムのみを許可すること。
2. 公開鍵暗号(RS256/ES256)の検討: 共通鍵の管理に不安がある場合、サーバー間で公開鍵・秘密鍵ペアを用いる RS256 を検討しましょう。これなら、署名には秘密鍵が必要なため、検証側のサーバーが万が一侵害されてもトークンの偽造は困難です。
3. トークンの有効期限(exp)の短縮: 攻撃されても被害を最小限にするため、リフレッシュトークンの仕組みを併用し、アクセストークンの寿命は数分〜数十分と短く設定してください。
4. aud (Audience) と iss (Issuer) のチェック: どのサービスが発行し、どのサービスが利用すべきかをトークン内に含め、バリデーション時に必ずチェックしてください。
—
最後に:インフラ屋の視点から
JWTは強力な武器ですが、ネットワークプロトコルの基本と同じく、「信頼」の定義をあやふやにすると必ず破綻します。「送られてきたデータを信じるな、検証ロジックを疑え」。これが、トラブルの現場で生き残ってきたエンジニアたちの不変の哲学です。
APIの設計が美しくても、認証という基盤が脆ければ、その上に乗るビジネスロジックは砂上の楼閣です。ぜひ、今日から実装を見直してみてください。それでは、また現場でお会いしましょう。
コメント