脆弱なJWT実装が招く「鍵なき認証」の深淵 —— alg: none 攻撃のパケット構造と、現場で磨いた防御の鉄則
ネットワークの深淵を覗くとき、我々はしばしば「信頼」という言葉の脆さを痛感する。TLSハンドシェイクで暗号化された安全なトンネルを通ってきたはずのJSON Web Token(JWT)が、アプリケーション層のわずかな論理的欠陥によって、いとも簡単に骨抜きにされる。
今回は、Web APIのセキュリティにおいて最も初歩的でありながら、今なお現場で後を絶たない alg: none 攻撃について、パケットレベルの挙動とインフラアーキテクトとしての防御哲学を紐解いていこう。
JWTの構造と「None」アルゴリズムの罠
JWTは、Header.Payload.Signature という3つのパートで構成される。本来、Header 部にはアルゴリズムを指定する alg フィールドがあり、ここで HS256 や RS256 を指定することで、署名の検証方法が定義される。
攻撃者は、この alg を none に書き換える。もしサーバー側のライブラリが、この「アルゴリズム未指定」という状態を「検証不要(署名なし)」と誤って解釈した場合、インフラ側でどれだけ強固なTLS 1.3を実装していようが、攻撃者は Payload を改ざんし、任意のユーザー権限でAPIを叩くことが可能になる。
パケットキャプチャが暴く「鍵なき改ざん」
Wiresharkでこの攻撃を覗くと、TLSでカプセル化されたHTTPリクエストの中に、以下のような Authorization ヘッダーが平然と流れているのが見て取れる。
/* 攻撃者が細工したヘッダー(Base64URLエンコード前) */
{
"alg": "none",
"typ": "JWT"
}
このリクエストを受け取ったバックエンドの検証ロジックが、「アルゴリズムが none ならば、署名確認はスキップしてよい」という判断を下した瞬間、認証の堅牢性はゼロになる。これは単なるコードのバグではなく、プロトコルの仕様を逆手に取った「設計の敗北」だ。
防御の最前線:アルゴリズムの固定検証
インフラアーキテクトとして、我々が取るべき対策は明確だ。脆弱なライブラリの挙動に依存するのではなく、「期待するアルゴリズム以外を一切受け付けない」という制約を、アプリケーションの境界(ゲートウェイ)で強制する。
以下は、Python(PyJWT)を用いた、堅牢な検証実装の例だ。
import jwt
# 信頼できないヘッダーを無視し、必ず特定のアルゴリズムを強制する
def verify_token(token, secret_key):
try:
# alg: none を許容しないよう、algorithms引数で固定する
# これにより、ヘッダーに none が指定されていても検証段階で例外が発生する
return jwt.decode(
token,
secret_key,
algorithms=["HS256"] # ここを明示的に指定することが防御の要
)
except jwt.InvalidAlgorithmError:
# ログには詳細なヘッダー情報を残し、監視システムにアラートを飛ばす
log_security_incident("不正なアルゴリズムが検出されました")
raise
パフォーマンスとセキュリティの二律背反を解消する
セキュリティを強化すると、往々にしてパフォーマンスが犠牲になる。しかし、現代のインフラでは「暗号化=低速」という常識は過去のものだ。
1. TLS 1.3とRTTの削減
JWTの検証コストを最小化するためには、検証までのパスを最短にする必要がある。TLS 1.3を採用することで、ハンドシェイクのRTT(往復時間)を削減し、0-RTTデータ転送を活用してコネクション確立のオーバーヘッドを極限まで削ぎ落とす。
2. TCPバッファチューニングの極意
高トラフィックなAPIサーバーでは、カーネルのTCPバッファ設定がボトルネックになることが多い。JWT検証の負荷が高い場合、sysctl で以下のパラメーターを調整し、ネットワークスタックの詰まりを解消しておくことが肝要だ。
# TCPウィンドウサイズの拡大とメモリ最適化
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
結びに代えて:プロトコルへの敬意
JWTの alg: none 脆弱性は、技術の進歩とともに忘れ去られる類のものではない。むしろ、HTTP/3やgRPCといった新しいプロトコルが普及しても、認証の根幹にある「信頼のロジック」は変わらない。
私たちが書くコードは、単なる機能実装ではない。それはネットワークという荒野を駆け巡るパケットを守るための「防壁」そのものだ。プロトコルの仕様を隅々まで読み込み、ライブラリの内部挙動を疑い、パケットの呼吸を感じる。その泥臭い執念こそが、真のインフラアーキテクトを形作るのだと、私は信じている。
次回の記事では、JWT の kid(Key ID)フィールドを悪用した Key Injection 攻撃について、さらに深く潜り込んでいこうと思う。準備はいいか。ネットワークの深淵は、いつでもあなたを待っている。
コメント