testing-oauth2-implementation-flaws

测试 OAuth 2.0 和 OpenID Connect 实现中的安全缺陷,包括授权码拦截、重定向 URI 操控、OAuth 流程中的 CSRF、令牌泄露、权限范围(scope)提升以及 PKCE 绕过。测试人员对授权服务器、客户端应用及令牌处理进行评估,发现可导致账户接管或未授权访问的常见错误配置。适用于 OAuth 安全测试、OIDC 漏洞评估、OAuth2 重定向绕过或授权码流程测试相关请求。

9 stars

Best use case

testing-oauth2-implementation-flaws is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

测试 OAuth 2.0 和 OpenID Connect 实现中的安全缺陷,包括授权码拦截、重定向 URI 操控、OAuth 流程中的 CSRF、令牌泄露、权限范围(scope)提升以及 PKCE 绕过。测试人员对授权服务器、客户端应用及令牌处理进行评估,发现可导致账户接管或未授权访问的常见错误配置。适用于 OAuth 安全测试、OIDC 漏洞评估、OAuth2 重定向绕过或授权码流程测试相关请求。

Teams using testing-oauth2-implementation-flaws should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/testing-oauth2-implementation-flaws/SKILL.md --create-dirs "https://raw.githubusercontent.com/killvxk/cybersecurity-skills-zh/main/skills/testing-oauth2-implementation-flaws/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/testing-oauth2-implementation-flaws/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How testing-oauth2-implementation-flaws Compares

Feature / Agenttesting-oauth2-implementation-flawsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

测试 OAuth 2.0 和 OpenID Connect 实现中的安全缺陷,包括授权码拦截、重定向 URI 操控、OAuth 流程中的 CSRF、令牌泄露、权限范围(scope)提升以及 PKCE 绕过。测试人员对授权服务器、客户端应用及令牌处理进行评估,发现可导致账户接管或未授权访问的常见错误配置。适用于 OAuth 安全测试、OIDC 漏洞评估、OAuth2 重定向绕过或授权码流程测试相关请求。

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 测试 OAuth2 实现缺陷

## 适用场景

- 评估 OAuth 2.0 授权码流程中重定向 URI 校验的薄弱环节
- 测试 OAuth 客户端应用的 CSRF 防护(state 参数使用)及 PKCE 强制执行
- 评估 OAuth 实现中的令牌存储、传输和生命周期管理
- 测试客户端请求超出授权范围的权限(scope)提升
- 评估 OpenID Connect 实现中的 ID 令牌校验和 nonce 使用

**不适用于**未经书面授权的情况。OAuth 测试可能导致令牌窃取或未授权访问。

## 前置条件

- 书面授权,明确在测试范围内的 OAuth 提供商和客户端应用
- 在授权服务器注册的测试 OAuth 客户端
- Burp Suite Professional,用于拦截 OAuth 重定向和令牌流
- Python 3.10+,安装 `requests` 和 `oauthlib` 库
- 浏览器开发者工具,用于观察 OAuth 重定向链
- 了解所使用的 OAuth 2.0 授权类型(authorization code、implicit、client credentials)

## 工作流程

### 步骤 1:OAuth 流程信息收集

```python
import requests
import urllib.parse
import re
import hashlib
import base64
import secrets

AUTH_SERVER = "https://auth.example.com"
CLIENT_ID = "test-client-id"
REDIRECT_URI = "https://app.example.com/callback"
SCOPE = "openid profile email"

# 发现 OAuth 端点
well_known = requests.get(f"{AUTH_SERVER}/.well-known/openid-configuration")
if well_known.status_code == 200:
    config = well_known.json()
    print("OAuth/OIDC 配置:")
    print(f"  授权端点: {config.get('authorization_endpoint')}")
    print(f"  令牌端点: {config.get('token_endpoint')}")
    print(f"  用户信息端点: {config.get('userinfo_endpoint')}")
    print(f"  JWKS: {config.get('jwks_uri')}")
    print(f"  支持的授权类型: {config.get('grant_types_supported')}")
    print(f"  支持的 scope: {config.get('scopes_supported')}")
    print(f"  PKCE 方法: {config.get('code_challenge_methods_supported')}")
    auth_endpoint = config['authorization_endpoint']
    token_endpoint = config['token_endpoint']
else:
    # 尝试常见路径
    for path in ["/authorize", "/oauth/authorize", "/oauth2/authorize", "/auth"]:
        resp = requests.get(f"{AUTH_SERVER}{path}", allow_redirects=False)
        if resp.status_code in (302, 400):
            print(f"发现授权端点: {AUTH_SERVER}{path}")
            auth_endpoint = f"{AUTH_SERVER}{path}"
            break
```

### 步骤 2:重定向 URI 校验测试

```python
# 测试 redirect_uri 校验严格程度
REDIRECT_BYPASS_PAYLOADS = [
    # 开放重定向变体
    REDIRECT_URI,                                          # 合法值
    "https://evil.com",                                    # 不同域名
    "https://app.example.com.evil.com/callback",          # 攻击者子域名
    "https://app.example.com@evil.com/callback",          # URL authority 混淆
    f"{REDIRECT_URI}/../../../evil.com",                  # 路径遍历
    f"{REDIRECT_URI}?next=https://evil.com",              # 参数注入
    f"{REDIRECT_URI}#https://evil.com",                   # 片段注入
    f"{REDIRECT_URI}%23evil.com",                         # 编码片段
    "https://app.example.com/callback/../../evil",        # 相对路径
    "https://APP.EXAMPLE.COM/callback",                   # 大小写变体
    "https://app.example.com/Callback",                   # 路径大小写变体
    "https://app.example.com/callback/",                  # 末尾斜杠
    "https://app.example.com/callback?",                  # 末尾问号
    "http://app.example.com/callback",                    # HTTP 降级
    "https://app.example.com:443/callback",               # 显式端口
    "https://app.example.com:8443/callback",              # 不同端口
    f"{REDIRECT_URI}/.evil.com",                          # 点号段
    "https://app.example.com/callbackevil",               # 路径前缀匹配
    "javascript://app.example.com/callback%0aalert(1)",   # JavaScript 协议
]

print("=== 重定向 URI 校验测试 ===\n")
for redirect in REDIRECT_BYPASS_PAYLOADS:
    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": redirect,
        "scope": SCOPE,
        "state": secrets.token_urlsafe(32),
    }
    resp = requests.get(auth_endpoint, params=params, allow_redirects=False)

    if resp.status_code == 302:
        location = resp.headers.get("Location", "")
        if "code=" in location or redirect in location:
            status = "已接受"
            if redirect != REDIRECT_URI:
                print(f"  [存在漏洞] {redirect[:70]} -> 重定向已被接受")
        else:
            status = "已重定向"
    elif resp.status_code == 400:
        status = "已拒绝"
    else:
        status = f"HTTP {resp.status_code}"

    if redirect == REDIRECT_URI:
        print(f"  [基准] {redirect[:70]} -> {status}")
```

### 步骤 3:State 参数(CSRF)测试

```python
# 测试 1:缺少 state 参数
params_no_state = {
    "response_type": "code",
    "client_id": CLIENT_ID,
    "redirect_uri": REDIRECT_URI,
    "scope": SCOPE,
}
resp = requests.get(auth_endpoint, params=params_no_state, allow_redirects=False)
if resp.status_code == 302 and "code=" in resp.headers.get("Location", ""):
    print("[CSRF] 授权码在没有 state 参数的情况下被颁发")

# 测试 2:state 参数复用
state_value = "fixed_state_value_123"
# 对多个授权请求使用相同的 state
for i in range(3):
    params = {**params_no_state, "state": state_value}
    resp = requests.get(auth_endpoint, params=params, allow_redirects=False)
    if resp.status_code == 302:
        location = resp.headers.get("Location", "")
        returned_state = urllib.parse.parse_qs(
            urllib.parse.urlparse(location).query).get("state", [None])[0]
        if returned_state == state_value:
            print(f"[信息] 第 {i+1} 次尝试时接受了相同的 state(请检查客户端校验)")

# 测试 3:不进行 state 校验的令牌交换(客户端检查)
# 拦截回调并尝试在不带 state 的情况下交换授权码
print("\n注意: state 校验是客户端行为,请验证回调处理器在交换授权码前是否校验了 state。")
```

### 步骤 4:PKCE 绕过测试

```python
# 测试是否强制要求 PKCE(Proof Key for Code Exchange,代码交换证明密钥)

# 生成 PKCE 值
code_verifier = secrets.token_urlsafe(64)[:128]
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')

# 测试 1:不带 PKCE 的授权请求
params_no_pkce = {
    "response_type": "code",
    "client_id": CLIENT_ID,
    "redirect_uri": REDIRECT_URI,
    "scope": SCOPE,
    "state": secrets.token_urlsafe(32),
}
resp = requests.get(auth_endpoint, params=params_no_pkce, allow_redirects=False)
if resp.status_code == 302 and "code=" in resp.headers.get("Location", ""):
    print("[PKCE] 未携带 code_challenge 即颁发了授权码")

# 测试 2:不带 code_verifier 的令牌交换
auth_code = "captured_auth_code"  # 从拦截中获取
token_resp = requests.post(token_endpoint, data={
    "grant_type": "authorization_code",
    "code": auth_code,
    "redirect_uri": REDIRECT_URI,
    "client_id": CLIENT_ID,
    # 不携带 code_verifier
})
if token_resp.status_code == 200:
    print("[PKCE] 未携带 code_verifier 即颁发了令牌——PKCE 未被强制执行")

# 测试 3:使用错误的 code_verifier 进行令牌交换
token_resp = requests.post(token_endpoint, data={
    "grant_type": "authorization_code",
    "code": auth_code,
    "redirect_uri": REDIRECT_URI,
    "client_id": CLIENT_ID,
    "code_verifier": "wrong_verifier_value_that_does_not_match",
})
if token_resp.status_code == 200:
    print("[PKCE] 使用错误的 code_verifier 仍颁发了令牌——PKCE 校验已损坏")

# 测试 4:从 S256 降级为 plain 方法
params_plain_pkce = {
    **params_no_pkce,
    "code_challenge": code_verifier,  # plain 方法中 challenge 即为 verifier 本身
    "code_challenge_method": "plain",
}
resp = requests.get(auth_endpoint, params=params_plain_pkce, allow_redirects=False)
if resp.status_code == 302:
    print("[PKCE] 接受了 plain challenge 方法——存在拦截风险")
```

### 步骤 5:Scope 提升与令牌测试

```python
# 测试 1:请求超出已注册范围的额外 scope
elevated_scopes = [
    "openid profile email admin",
    "openid profile email write:users",
    "openid profile email delete:*",
    "openid profile email admin:full",
    "*",
]

for scope in elevated_scopes:
    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "scope": scope,
        "state": secrets.token_urlsafe(32),
    }
    resp = requests.get(auth_endpoint, params=params, allow_redirects=False)
    if resp.status_code == 302:
        location = resp.headers.get("Location", "")
        if "code=" in location:
            print(f"[SCOPE] 提升的 scope 已被接受: {scope}")

# 测试 2:跨客户端令牌复用
# 将客户端 A 的令牌用于客户端 B 的 API
token_a = "access_token_from_client_a"
resp = requests.get("https://other-service.example.com/api/resource",
    headers={"Authorization": f"Bearer {token_a}"})
if resp.status_code == 200:
    print("[令牌] 客户端 A 的令牌被其他服务接受(未校验 audience)")

# 测试 3:刷新令牌窃取与复用
refresh_token = "captured_refresh_token"
# 尝试用不同的 client_id 使用刷新令牌
token_resp = requests.post(token_endpoint, data={
    "grant_type": "refresh_token",
    "refresh_token": refresh_token,
    "client_id": "different-client-id",
})
if token_resp.status_code == 200:
    print("[令牌] 刷新令牌被不同客户端接受——令牌未绑定到特定客户端")
```

### 步骤 6:隐式流程与令牌泄露测试

```python
# 测试是否启用了隐式流程(根据 OAuth 2.1 应禁用)
implicit_params = {
    "response_type": "token",
    "client_id": CLIENT_ID,
    "redirect_uri": REDIRECT_URI,
    "scope": SCOPE,
    "state": secrets.token_urlsafe(32),
}
resp = requests.get(auth_endpoint, params=implicit_params, allow_redirects=False)
if resp.status_code == 302:
    location = resp.headers.get("Location", "")
    if "access_token=" in location:
        print("[隐式流程] 隐式流程已启用——令牌出现在 URL 片段中(已废弃/不安全)")

# 通过 Referer 头部检查令牌泄露
print("\n令牌泄露检查:")
print("  - 检查访问令牌是否出现在 URL 查询参数中")
print("  - 检查令牌是否被记录在服务器访问日志中")
print("  - 检查含授权码的回调 URL 是否被浏览器缓存")
print("  - 检查授权码是否为一次性使用(重放测试)")

# 授权码重放测试
auth_code_to_replay = "captured_auth_code"
for attempt in range(3):
    token_resp = requests.post(token_endpoint, data={
        "grant_type": "authorization_code",
        "code": auth_code_to_replay,
        "redirect_uri": REDIRECT_URI,
        "client_id": CLIENT_ID,
        "client_secret": "client_secret_value",
    })
    print(f"  授权码重放尝试 {attempt+1}: {token_resp.status_code}")
    if attempt > 0 and token_resp.status_code == 200:
        print("  [存在漏洞] 授权码不是一次性使用")
```

## 核心概念

| 术语 | 定义 |
|------|------|
| **授权码流程(Authorization Code Flow)** | OAuth 2.0 流程,客户端通过重定向接收授权码,再在令牌端点将其交换为令牌 |
| **PKCE** | Proof Key for Code Exchange(代码交换证明密钥)——通过 code verifier/challenge 将授权请求与令牌请求绑定,防止授权码拦截攻击 |
| **重定向 URI 校验(Redirect URI Validation)** | 授权服务器验证 redirect_uri 与注册值完全匹配,防止通过开放重定向窃取授权码/令牌 |
| **State 参数** | 在授权请求中传递的随机值,在回调中进行验证,以防止对 OAuth 流程的 CSRF 攻击 |
| **Scope 提升(Scope Escalation)** | 请求或获取超出客户端授权范围的权限(scope),从而实现未授权访问 |
| **隐式流程(Implicit Flow)** | 已废弃的 OAuth 流程,令牌直接出现在 URL 片段中,易遭受令牌泄露和重放攻击 |

## 工具与系统

- **Burp Suite Professional**:拦截并操控 OAuth 重定向、授权码和令牌交换
- **EsPReSSO(Burp 扩展)**:自动测试 OAuth 和 OpenID Connect 实现中的已知漏洞
- **oauth2-security-tester**:专用工具,用于针对常见攻击模式测试 OAuth 2.0 流程
- **OWASP ZAP**:被动扫描器,检测拦截流量中的 OAuth 错误配置
- **jwt.io**:在线 JWT 解码工具,用于分析 OAuth 访问令牌和 ID 令牌

## 常见场景

### 场景:社会化登录 OAuth 实现评估

**场景背景**:某 Web 应用使用 OAuth 2.0 授权码流程实现了"使用 Google 登录"和"使用 GitHub 登录"功能。该应用是一个 SaaS 平台,账户接管具有较高业务影响。

**方法**:
1. 分析两个提供商的 `/.well-known/openid-configuration` OAuth 配置
2. 测试重定向 URI 校验:发现应用注册了 `https://app.example.com/callback`,但服务器接受了 `https://app.example.com/callback/..%2fevil`
3. 测试 state 参数:授权请求包含 state,但回调处理器未对其进行校验(存在 CSRF 风险)
4. 测试 PKCE:授权码流程未实现 PKCE,移动端存在授权码拦截风险
5. 测试隐式流程:应用未使用,但服务器仍已启用
6. 测试 scope:应用请求 `openid profile email`,但授权服务器在未明确授权的情况下也授予了 `read:repos`
7. 测试授权码重放:授权码可被交换两次,表明缺乏一次性使用强制执行
8. 测试令牌 audience:Google 登录获取的访问令牌被 GitHub API 端点接受(未校验 audience)

**常见陷阱**:
- 仅在浏览器中测试 OAuth 流程,而不拦截和操控重定向参数
- 未单独测试授权请求和令牌交换两个阶段
- 遗漏应用中可与 OAuth redirect_uri 链式组合的开放重定向漏洞
- 未在客户端侧测试 state 参数校验(服务器可能包含 state,但客户端可能未检查)
- 误以为授权服务器支持 PKCE 就等于已强制执行(客户端也必须发送)

## 输出格式

```
## 发现:OAuth2 重定向 URI 绕过导致授权码被窃取

**ID**: API-OAUTH-001
**严重程度**: 严重 (CVSS 9.3)
**受影响组件**: OAuth 2.0 授权码流程
**授权服务器**: auth.example.com

**描述**:
授权服务器的 redirect_uri 校验使用前缀匹配而非精确字符串匹配。
攻击者可操控 redirect_uri,将授权码重定向到攻击者控制的端点,
从而实现账户接管。此外,PKCE 未被强制执行,客户端应用也未校验 state 参数。

**概念验证**:
1. 构造含操控 redirect_uri 的授权 URL:
   https://auth.example.com/authorize?response_type=code&client_id=app
   &redirect_uri=https://app.example.com/callback/../../../evil.com
   &scope=openid+profile+email&state=abc123
2. 用户进行身份验证并批准授权
3. 授权码被重定向至 https://evil.com?code=AUTH_CODE&state=abc123
4. 攻击者在令牌端点交换授权码(无需 PKCE)
5. 攻击者获取受害者账户的访问令牌和 ID 令牌

**影响**:
任何点击精心构造的 OAuth 登录链接的用户均可遭受完整账户接管。
攻击者将获得对用户资料、邮箱及 OAuth scope 授权访问的所有资源的完整控制权。

**修复建议**:
1. 对 redirect_uri 实施精确字符串匹配(无通配符,无前缀匹配)
2. 对所有授权码流程请求强制要求 PKCE(S256 方法)
3. 在回调处理器中交换授权码前校验 state 参数
4. 在授权服务器上禁用隐式流程
5. 强制授权码为一次性使用,并设置较短的 TTL(最多 60 秒)
6. 在接受令牌前校验 audience (aud) 声明
```

Related Skills

testing-websocket-api-security

9
from killvxk/cybersecurity-skills-zh

测试 WebSocket API 实现中的安全漏洞,包括 WebSocket 升级时缺少身份认证、跨站 WebSocket 劫持(Cross-Site WebSocket Hijacking,CSWSH)、通过 WebSocket 消息进行的注入攻击、输入校验不足、通过消息泛洪实施拒绝服务,以及通过 WebSocket 帧造成的信息泄露。测试人员使用 Burp Suite 拦截 WebSocket 握手和消息,构造恶意 payload,并测试 WebSocket 通道上的授权绕过。适用于 WebSocket 安全测试、WS 渗透测试、CSWSH 攻击或实时 API 安全评估相关请求。

testing-mobile-api-authentication

9
from killvxk/cybersecurity-skills-zh

测试移动应用 API 的认证与授权机制,识别认证失效、不安全的令牌管理、会话固定、 权限提升和 IDOR 漏洞。适用于对移动应用后端进行 API 安全评估、测试 JWT 实现、 评估 OAuth 流程或评估会话管理的场景。适合涉及移动 API 认证测试、令牌安全评估、 OAuth 移动端流程测试或 API 授权绕过的相关请求。

testing-jwt-token-security

9
from killvxk/cybersecurity-skills-zh

在安全测试活动中,评估 JSON Web Token(JWT)实现中的密码学弱点、算法混淆攻击和授权绕过漏洞。

testing-for-xxe-injection-vulnerabilities

9
from killvxk/cybersecurity-skills-zh

在授权的渗透测试中发现和利用 XML 外部实体(XXE)注入漏洞,以读取服务器文件、执行 SSRF 并外泄数据。

testing-for-xss-vulnerabilities

9
from killvxk/cybersecurity-skills-zh

通过向反射型、存储型和 DOM 型上下文注入 JavaScript 载荷,测试 Web 应用程序的跨站脚本(XSS)漏洞, 演示客户端代码执行、会话劫持和用户冒充。测试人员识别所有注入点和输出上下文,构造适合上下文的载荷, 并绕过净化和 CSP 保护。适用于 XSS 测试、跨站脚本评估、客户端注入测试或 JavaScript 注入漏洞测试等请求场景。

testing-for-xss-vulnerabilities-with-burpsuite

9
from killvxk/cybersecurity-skills-zh

在授权的安全评估过程中,使用 Burp Suite 的扫描器、Intruder 和 Repeater 工具识别和验证跨站脚本(XSS)漏洞。适用于 Web 应用渗透测试中检测反射型、存储型和 DOM 型 XSS,验证自动化扫描器报告的 XSS 发现,以及评估 CSP 和 XSS 过滤器的有效性时使用。

testing-for-xml-injection-vulnerabilities

9
from killvxk/cybersecurity-skills-zh

测试 Web 应用程序中的 XML 注入漏洞,包括 XXE(XML 外部实体注入)、XPath 注入和 XML 实体攻击,以识别数据泄露和服务器端请求伪造(SSRF)风险。

testing-for-sensitive-data-exposure

9
from killvxk/cybersecurity-skills-zh

在安全评估中识别敏感数据暴露漏洞,包括 API 密钥泄露、响应中的 PII、不安全存储以及未受保护的数据传输。

testing-for-open-redirect-vulnerabilities

9
from killvxk/cybersecurity-skills-zh

通过分析 URL 重定向参数、绕过技术和利用链,识别并测试 Web 应用程序中的开放重定向漏洞,用于网络钓鱼和 Token 窃取。

testing-for-json-web-token-vulnerabilities

9
from killvxk/cybersecurity-skills-zh

测试 JWT 实现中的关键漏洞,包括算法混淆、none 算法绕过、kid 参数注入和弱密钥利用,以实现认证绕过和权限提升。

testing-for-host-header-injection

9
from killvxk/cybersecurity-skills-zh

测试 Web 应用程序的 HTTP Host 头部注入漏洞,以识别密码重置中毒、Web 缓存投毒、SSRF 以及虚拟主机路由操控风险。

testing-for-email-header-injection

9
from killvxk/cybersecurity-skills-zh

测试 Web 应用程序邮件功能中的 SMTP 头部注入漏洞,这些漏洞允许攻击者注入额外的邮件头部、修改收件人,并通过联系表单实施垃圾邮件中继。