侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计防御OAuth 2.0授权码注入攻击的安全方案

2025-12-12 / 0 评论 / 2 阅读

题目

设计防御OAuth 2.0授权码注入攻击的安全方案

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

OAuth 2.0协议安全, CSRF防御, PKCE机制, 重定向URI验证, 令牌安全存储

快速回答

防御OAuth 2.0授权码注入的核心方案:

  • 强制实施PKCE(Proof Key for Code Exchange)机制
  • 严格验证重定向URI的完整性和白名单匹配
  • 使用加密的state参数防御CSRF
  • 授权码单次有效且短生命周期(≤10分钟)
  • 客户端认证与令牌绑定(Token Binding)
## 解析

原理说明

授权码注入攻击发生在攻击者窃取授权码并注入到自己的客户端中。OAuth 2.0的脆弱点包括:

  • 授权码劫持:通过中间人攻击或开放重定向窃取授权码
  • CSRF:诱导用户使用攻击者的授权码完成认证流程
  • 客户端冒充:未经验证的客户端使用窃取的授权码获取令牌

防御方案与代码示例

1. PKCE 实现(关键防御)

// 客户端生成PKCE参数
const crypto = require('crypto');

// 步骤1:生成code_verifier(43-128字符)
const codeVerifier = crypto.randomBytes(32).toString('base64')
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=/g, '');

// 步骤2:生成code_challenge
const codeChallenge = crypto.createHash('sha256')
  .update(codeVerifier)
  .digest('base64')
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=/g, '');

// 授权请求中添加参数
const authUrl = `https://auth-server/authorize?response_type=code
  &client_id=CLIENT_ID
  &redirect_uri=https://client/callback
  &code_challenge=${codeChallenge}
  &code_challenge_method=S256
  &state=CRYPTO_RANDOM_STATE`;

服务端验证逻辑

  • 存储授权请求中的code_challenge
  • 兑换令牌时验证code_verifier的哈希匹配code_challenge

2. 重定向URI安全验证

# 服务端验证示例 (Python/Flask)
def validate_redirect_uri(client_id, redirect_uri):
    # 从数据库获取客户端注册的URI白名单
    allowed_uris = get_client_redirect_uris(client_id)

    # 严格匹配(包括query参数)
    if redirect_uri not in allowed_uris:
        abort(400, "Invalid redirect URI")

    # 额外检查:防止开放重定向
    parsed = urlparse(redirect_uri)
    if parsed.scheme != "https" or not parsed.hostname.endswith(".trusted-domain.com"):
        abort(400, "Untrusted redirect target")

3. State参数加密处理

// Java示例:生成加密state
public String generateSecureState(String sessionId) {
    String state = UUID.randomUUID().toString();
    // 绑定会话ID并加密(使用AES-GCM)
    String payload = sessionId + "|" + state;
    return encrypt(payload, SECRET_KEY);
}

// 回调时验证
public void validateState(String encryptedState, HttpSession session) {
    String payload = decrypt(encryptedState, SECRET_KEY);
    String[] parts = payload.split("\\|");
    if (!session.getId().equals(parts[0])) {
        throw new SecurityException("State session mismatch");
    }
    // 验证state随机值未被篡改
}

最佳实践

  • 令牌安全
    • 访问令牌有效期≤1小时,刷新令牌≤90天
    • 使用JWT格式令牌时添加`jti`(JWT ID)防止重放
  • 客户端分级
    • 公共客户端(SPA/移动端)必须使用PKCE
    • 机密客户端(服务端)需定期轮换client_secret
  • 监控审计
    • 记录授权码使用IP/设备指纹异常
    • 实时警报多次失败的令牌兑换请求

常见错误

  • 重定向URI验证不足
    • 错误:仅验证域名不验证完整路径,导致子路径开放重定向
    • 修复:严格全匹配注册URI(含协议/端口/路径)
  • PKCE实现缺陷
    • 错误:使用`code_challenge_method=plain`(明文传输)
    • 修复:强制使用`S256`哈希算法
  • 令牌存储泄露
    • 错误:浏览器localStorage存储访问令牌
    • 修复:机密数据使用HttpOnly Cookie + 服务端会话存储

扩展知识

  • OAuth 2.1规范
    • 强制要求PKCE(RFC 7636)
    • 废除隐式授权(implicit grant)
    • 重定向URI必须精确匹配
  • 进阶防御
    • 令牌绑定(Token Binding):将令牌与TLS会话关联
    • Dpop(Demonstrated Proof-of-Possession):客户端密钥证明
  • 相关漏洞案例
    • Facebook 2018漏洞:重定向URI解析差异导致令牌泄露
    • Azure AD 2021漏洞:PKCE实现缺陷允许授权码注入