第三章:身份认证绕过——JWT令牌伪造与破解、OAuth 2.0授权码拦截、Session固定攻击
身份认证,说白了就是系统判断「你是谁」的过程。但很多开发者在实现时,往往只关注功能跑通,忽略了认证机制本身的安全隐患。我做过不少渗透测试项目,说实话,认证绕过是最容易出成果的方向之一。今天我们就来聊聊三种最常见的攻击手法:JWT伪造、OAuth授权码拦截、Session固定攻击。
3.1 JWT令牌伪造与破解
JWT(JSON Web Token)现在几乎是API认证的标配。它长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
三个部分用点号分隔:Header、Payload、Signature。很多人以为JWT很安全,其实不然。
3.1.1 算法混淆攻击
我遇到过最典型的案例——某金融平台使用了JWT,但服务端没有限制签名算法。攻击者把Header中的alg从RS256改成HS256,然后用公钥作为密钥去签名。你想想看,公钥是公开的,这不就等于把钥匙挂门口了吗?
攻击步骤:
- 获取服务端公钥(通常从
/.well-known/jwks.json获取) - 修改JWT Header:
{"alg":"HS256","typ":"JWT"} - 修改Payload中的用户身份信息
- 用公钥作为HMAC密钥重新签名
# Python示例:算法混淆攻击
import jwt
public_key = open('public.pem').read()
payload = {'sub': 'admin', 'role': 'administrator'}
# 使用公钥作为HS256的密钥
forged_token = jwt.encode(payload, public_key, algorithm='HS256')
print(forged_token)
⚠️ 注意:服务端必须明确指定允许的算法列表,不能依赖JWT头部的alg字段。我建议在代码中硬编码algorithms=['RS256'],而不是从请求中读取。
3.1.2 空签名攻击
还有一种更离谱的情况——有些JWT库允许alg设置为none。这意味着不需要签名验证。我在一次红队演练中,直接修改了JWT的alg为none,然后删掉签名部分,居然成功冒充了管理员。
# 修改后的JWT
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.
嗯,这里要注意:服务端一定要拒绝alg: none的令牌。我个人的习惯是在代码里加一个白名单校验:
# 安全的JWT验证方式
ALLOWED_ALGORITHMS = ['RS256', 'ES256']
def verify_jwt(token):
try:
# 明确指定允许的算法
payload = jwt.decode(token, public_key, algorithms=ALLOWED_ALGORITHMS)
return payload
except jwt.InvalidAlgorithmError:
raise Exception("不支持的签名算法")
3.1.3 密钥爆破
如果服务端用了弱密钥,比如secret、123456这种,那基本上等于没加密。我常用的工具是jwt_tool和hashcat:
# 使用jwt_tool进行密钥爆破
python jwt_tool.py <JWT_TOKEN> -C -d /usr/share/wordlists/rockyou.txt
# 使用hashcat
hashcat -m 16500 <JWT_TOKEN> /usr/share/wordlists/rockyou.txt
💡 我的建议:生产环境中JWT密钥至少256位随机字符串,并且定期轮换。我曾经见过一个项目用了jwt_secret作为密钥,结果被爆破只用了3秒。
3.2 OAuth 2.0授权码拦截
OAuth 2.0授权码流程看起来挺安全——授权码是一次性的,而且需要client_secret才能换取access_token。但实际攻击面比你想的多。
3.2.1 授权码劫持
攻击者通过钓鱼或中间人攻击,截获用户浏览器重定向中的授权码。为什么能成功?因为很多应用没有正确验证redirect_uri。
典型攻击场景:
- 攻击者构造恶意链接:
https://oauth-server.com/auth?response_type=code&client_id=xxx&redirect_uri=https://attacker.com/callback - 用户点击后,授权码直接发到攻击者服务器
- 攻击者用这个授权码换取access_token
为什么会这样?说白了就是服务端没有严格校验redirect_uri。我建议的做法是:
# 安全的redirect_uri校验
ALLOWED_REDIRECT_URIS = [
"https://myapp.com/callback",
"https://myapp.com/oauth/callback"
]
def validate_redirect_uri(uri):
# 必须完全匹配,不能只匹配前缀
if uri not in ALLOWED_REDIRECT_URIS:
raise Exception("非法的重定向地址")
return True
3.2.2 CSRF攻击与state参数
OAuth流程中如果没有state参数,攻击者可以诱导用户点击恶意链接,在用户不知情的情况下绑定攻击者的账户。我记得有个社交登录平台就出过这种问题。
⚠️ 避坑指南:我曾经审计过一个项目,他们觉得state参数「太麻烦」就没加。结果攻击者构造了一个链接,用户点击后自动绑定了攻击者的第三方账号。从那以后,我每次做安全评审都会重点检查state参数。
# 生成并验证state参数
import secrets
from flask import session
# 生成state
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
# 验证state
def verify_state(received_state):
expected_state = session.get('oauth_state')
if not expected_state or received_state != expected_state:
raise Exception("state参数不匹配,可能存在CSRF攻击")
session.pop('oauth_state', None)
3.3 Session固定攻击
Session固定攻击算是比较老的手法了,但到现在还有应用在犯。攻击者先获取一个有效的Session ID,然后诱导用户使用这个Session ID登录。登录后,攻击者就能用同一个Session ID访问用户账户。
3.3.1 攻击原理
你想想看,如果登录前后Session ID不变,那攻击者只要让用户用他指定的Session ID登录就行了。常见的传播方式有:
- URL参数:
http://example.com/?sessionid=abc123 - Cookie注入:通过XSS或HTTP响应头注入
- 钓鱼邮件中的链接
3.3.2 防御措施
防御其实很简单——登录成功后重新生成Session ID。我习惯在代码里这样处理:
# Flask示例:登录后重新生成session
from flask import session
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if authenticate(username, password):
# 关键步骤:重新生成session
session.clear()
session['user_id'] = get_user_id(username)
session['role'] = get_user_role(username)
return redirect('/dashboard')
else:
return '登录失败', 401
💡 我的经验:除了登录后重置Session,我还建议设置合理的Session过期时间。比如金融类应用,我一般建议15分钟无操作就自动登出。另外,绑定客户端信息也是个好办法——比如记录IP和User-Agent,如果登录后这些信息变了,就要求重新认证。
3.4 实战总结
这三种攻击手法,说白了都是认证机制实现上的漏洞。我整理了一个对比表:
| 攻击类型 | 根本原因 | 防御要点 | 检测难度 |
|---|---|---|---|
| JWT伪造 | 算法未限制、密钥太弱 | 白名单算法、强密钥、验证alg字段 | 低 |
| OAuth授权码拦截 | redirect_uri未严格校验、缺少state | 精确匹配URI、强制使用state参数 | 中 |
| Session固定攻击 | 登录后未重置Session ID | 登录成功立即重新生成Session | 低 |
最后说一句:认证安全没有银弹。我见过太多团队觉得「用了JWT就安全了」或者「OAuth是标准协议肯定没问题」。实际上,每个环节都可能出问题。我的习惯是——每做一个认证功能,先想想「如果我是攻击者,我会怎么绕过它?」。嗯,保持这种思维方式,你就能发现很多隐藏的漏洞。