一、JWT 基础认知
1. 定义与结构
JSON Web Token(JWT)是一种基于 JSON 的轻量级身份验证令牌,用于在客户端与服务端之间安全传递声明信息,常见于单点登录(SSO)场景。
-
结构组成:由 3 部分通过
.
拼接,分别为:-
Header(头部):Base64 编码的 JSON,包含令牌类型(
typ: "JWT"
)和签名算法(alg: "HS256"
等)。 -
Payload(负载):Base64 编码的 JSON,存储用户 ID、权限、过期时间等核心数据(如
{"user": "admin", "exp": 1620000000}
)。 -
Signature(签名):通过 Header 指定的算法,对
Header.Base64 + "." + Payload.Base64
与密钥计算得出的哈希值,用于验证令牌完整性。
-
二、核心攻击方式
1. 敏感信息泄露
原理
Header 和 Payload 采用可逆的 Base64URL 编码(非加密),任何人获取令牌后均可解码读取内容,若包含密码、密钥等敏感数据将直接泄露。
利用方法
-
解码工具:
-
Linux:
echo "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" | base64 -d
(输出:{"typ":"JWT","alg":"HS256"}
) -
浏览器控制台:
atob("eyJ1c2VyIjoiYWRtaW4ifQ")
(输出:{"user":"admin"}
) -
Python:
import base64 print(base64.b64decode("eyJ1c2VyIjoiYWRtaW4ifQ".encode()).decode())
-
风险案例
某网站在 Payload 中包含用户明文密码,攻击者截获令牌后直接解码获取,进而登录用户账户。
2. 算法置空攻击(alg: none)
原理
JWT 协议支持将签名算法设为"none"
,此时签名字段为空(仅保留.
),服务端若未禁用该配置,会跳过签名验证,直接信任令牌内容。
利用步骤
-
获取原始令牌: 例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-
修改 Header 算法为
none
: 解码 Header 并改为{"alg": "none", "typ": "JWT"}
,重新 Base64 编码为:eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
-
删除签名部分: 最终伪造令牌:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
(末尾保留.
) -
发送请求:若服务端接受该令牌并返回管理员权限页面,则漏洞存在。
防御建议
服务端配置中明确指定允许的签名算法(如仅支持HS256
),禁止"none"
算法。
3. 密钥混淆攻击(HS256 vs RS256)
原理
-
HS256:对称加密算法,使用同一密钥进行签名和验证。
-
RS256:非对称加密算法,使用私钥签名、公钥验证。 若服务端同时支持两种算法,攻击者可将
alg
从RS256
改为HS256
,诱使服务端用公钥作为 HS256 的密钥验证签名(公钥通常可公开获取)。
利用步骤
-
获取公钥:通过目标网站的
/.well-known/jwks.json
或证书文件提取公钥(如public.pem
)。 -
构造恶意令牌:
-
修改 Header 的
alg
为HS256
。 -
用获取的公钥作为密钥,对修改后的 Header 和 Payload 进行 HS256 签名。
-
-
发送请求:服务端若用公钥验证 HS256 签名,将认可伪造的令牌。
防御建议
服务端仅启用一种加密算法(如纯 RS256),并严格校验算法类型,拒绝非预期算法。
4. 无效签名绕过
原理
服务端未严格验证签名(如代码中省略签名校验步骤),攻击者可直接修改 Payload(如将user: "test"
改为user: "admin"
),无需重新签名即可通过验证。
利用案例
某网站 “个人资料页” 的访问控制依赖 JWT 中的user
字段,攻击者截获令牌后:
-
解码 Payload 并修改
user
为"admin"
,重新 Base64 编码。 -
保持原始签名不变(或随意修改),发送请求后成功访问管理员资料页。
5. 暴力破解密钥(针对 HMAC 算法)
原理
HS256/HS384 等对称算法的签名依赖密钥,若密钥强度低(如弱密码),可通过字典攻击离线破解。
利用工具与命令
-
jwt_tool(主流 JWT 测试工具):
# 用字典1.txt暴力破解密钥 python jwt_tool.py <目标令牌> -C -d 1.txt # 示例输出:[+] 密钥匹配:abc123
-
破解成功后:用密钥伪造任意令牌(如将
user
改为admin
,重新签名)。
6. 密钥泄露
原理
攻击者通过其他漏洞获取签名密钥(如 Git 信息泄露、目录遍历、XXE 等),直接伪造合法令牌。
常见泄露场景
-
代码仓库泄露:
.git
目录中遗留jwt_secret.key
文件。 -
任意文件读取:通过
../../etc/jwt/secret
路径读取密钥。 -
XXE 漏洞:利用 XML 外部实体注入读取密钥文件。
7. KID 参数操纵
原理
kid
(Key ID)是 Header 中的可选字段,用于指定验证密钥(如从文件系统或数据库中检索),若未过滤用户输入,可引发多种攻击。
具体利用
-
目录遍历: 构造
"kid": "../../../etc/passwd"
,使服务端用系统文件/etc/passwd
作为验证密钥,攻击者用该文件内容签名令牌。 -
SQL 注入: 若
kid
用于数据库查询(如SELECT key FROM keys WHERE id = '${kid}'
),可注入:"kid": "1' UNION SELECT 'my_secret';--"
,使服务端用my_secret
作为密钥。 -
命令注入: 若
kid
传入文件读取函数(如 Ruby 的open()
),可构造:"kid": "key.txt|whoami"
,执行系统命令并返回结果。
8. 头部参数操纵(JKU/JWK/X5U)
原理
-
JKU(JWK Set URL):指定包含验证密钥的 URL,服务端会自动下载并使用该 URL 中的密钥。
-
JWK(JSON Web Key):直接在 Header 中嵌入验证密钥。
-
X5U/X5C:指定包含公钥证书的 URL 或嵌入证书内容。
利用步骤
-
托管恶意密钥:攻击者在自己的服务器上放置恶意 JWK 文件(如
https://attacker.com/jwks.json
)。 -
构造令牌:Header 中设置
"jku": "https://attacker.com/jwks.json"
。 -
发送请求