# OAuth 2.0漏洞

## OAuth 2.0 授权框架

[OAuth 2.0](https://tools.ietf.org/html/rfc6749) 是一种协议，允许用户向第三方网站或应用程序授予访问其受保护资源的权限，而不必揭示其长期凭据甚至身份。

OAuth 2.0 引入了授权层，并将客户端的角色与资源所有者的角色分开。在OAuth 2.0中，客户端请求访问由资源所有者控制并由资源服务器托管的资源，并被授予一组与资源所有者不同的凭据。客户端不是使用资源所有者的凭据访问受保护资源，而是获得 `access token`。

访问令牌由授权服务器在资源所有者批准后颁发给第三方客户端。客户端使用访问令牌访问由资源服务器托管的受保护资源。

访问令牌所代表的权限，在OAuth 2.0术语中称为 `scopes`。

{% hint style="info" %}
Scope是一种机制，定义应用程序可以执行的具体操作或它们可以代表用户请求的信息。
{% endhint %}

### 访问令牌

[访问令牌](https://tools.ietf.org/html/rfc6749#section-1.4) 是应用程序可用于访问API的凭据。它通知API令牌持有者已被授权访问API并执行已授予scope指定的特定操作。

访问令牌可以是任何格式，但两个流行的选项包括不透明字符串和JSON Web令牌（JWT）。它们应该作为Bearer凭据在HTTP Authorization头中传输到API。

### 刷新令牌

[刷新令牌](https://tools.ietf.org/html/rfc6749#section-1.5) 是一种特殊类型的令牌，可用于获取更新的访问令牌（详见[此处](https://tools.ietf.org/html/rfc6749#section-6)）。它用于更新过期的访问令牌而无需强制用户再次登录。使用刷新令牌，您可以在刷新令牌被列入黑名单之前的任何时候请求新的访问令牌。

### OAuth 2.0 角色

在任何OAuth 2.0流程中，我们可以识别以下角色：

* `Resource Owner` - 可以授予访问受保护资源的实体；通常是最终用户。
* `Resource Server` - 托管受保护资源的服务器；这是您要访问的API。
* `Client` - 代表资源所有者请求访问受保护资源的应用程序。
* `Authorization Server` - 验证资源所有者身份并在获得适当授权后颁发访问令牌的服务器。

### 协议流程

OAuth 2.0有许多"风味"（称为授权授予类型），您可以使用这些类型。现在我们将更一般地了解流程。

![](/files/nIZKKXSH8cO0dczg68Og)

1. 应用程序（客户端）向资源所有者请求授权以访问资源。
2. 假设资源所有者授权此访问，应用程序接收授权授予。这是表示资源所有者授权的凭据。
3. 应用程序通过向授权服务器验证并提供授权授予来请求访问令牌。
4. 假设应用程序成功验证并且授权授予有效，授权服务器颁发访问令牌并将其发送到应用程序。
5. 应用程序向资源服务器请求访问受保护资源，并通过出示访问令牌进行验证。
6. 假设访问令牌有效，资源服务器为应用程序的请求提供服务。

### 授权授予类型

[OAuth 2.0 授权框架规范](https://tools.ietf.org/html/rfc6749) 定义了四种获取访问令牌的流程。这些流程称为授予类型。决定哪一种适合您的情况主要取决于您的应用程序类型。

* [授权码授予](https://tools.ietf.org/html/rfc6749#section-4.1) - 由在服务器上执行的Web应用程序使用。这也被移动应用程序使用，使用[代码交换证明密钥（PKCE）](https://tools.ietf.org/html/rfc7636)技术。
* [隐式授予](https://tools.ietf.org/html/rfc6749#section-4.2) - 由在用户浏览器上执行的以javascript为中心的应用程序（单页应用程序）使用。
* [资源所有者密码凭据授予](https://tools.ietf.org/html/rfc6749#section-4.3) - 由受信任的应用程序使用。
* [客户端凭据授予](https://tools.ietf.org/html/rfc6749#section-4.4) - 用于机器对机器通信。

该规范还提供了定义其他类型的扩展机制。

### OAuth 2.0 端点

OAuth 2.0 利用两个端点：

* 授权端点
* 令牌端点

#### 授权端点

[授权端点](https://tools.ietf.org/html/rfc6749#section-3.1) 用于与资源所有者交互并获取访问受保护资源的授权。

{% hint style="info" %}
授权端点由授权码和隐式授予使用。
{% endhint %}

为了更好地理解这一点，假设您想使用您的Google账户登录服务。首先，服务将您重定向到Google进行身份验证（如果您尚未登录），然后您将获得同意屏幕，其中您将被要求授权服务访问您的某些数据（受保护资源），例如您的电子邮件地址和联系人列表。

授权端点的请求参数是：

* `response_type` - 告诉授权服务器执行哪个授予。
* `client_id` - 请求授权的应用程序的id。
* `redirect_uri` - 保存一个URL；此端点的成功响应将导致重定向到此URL。
* `scope` - 应用程序所需的权限的空格分隔列表。
* `state` - 是一个参数，允许您恢复应用程序的先前状态；`state` 参数保留客户端在授权请求中设置的一些状态对象，并在响应中使其可用于客户端。

**response\_type如何工作**

`response_type` 请求参数用于通知授权服务器使用哪种授予类型：

* `code` - 使用授权码授予的值。
* `token` - 使用隐式授予的值。
* `password` - 使用资源所有者密码凭据授予的值。
* `client_credentials` - 使用客户端凭据授予的值。

**state如何工作**

以下是带有 `state` 参数的完整授权码授予的序列图。客户端通过检查当用户回来获取 `access_token` 时 `state` 是否存在于用户会话中来实现CSRF保护。在此设计中，`state` 参数是已验证用户与客户端应用程序会话中会话属性的键。

![](/files/q3F6C8Mv6jBqrZPoLrd1)

#### 令牌端点

[令牌端点](https://tools.ietf.org/html/rfc6749#section-3.2) 由客户端用于通过出示其授权授予或刷新令牌来获取[访问令牌](https://tools.ietf.org/html/rfc6749#section-1.4)。令牌端点需要客户端验证，详见[此处](https://tools.ietf.org/html/rfc6749#section-3.2.1)。

{% hint style="info" %}
令牌端点与除隐式授予类型外的每个授权授予一起使用（因为直接颁发访问令牌）。
{% endhint %}

### 授权码授予

由于常规Web应用程序是服务器端应用程序，其源代码不公开暴露，它们可以使用[授权码授予](https://tools.ietf.org/html/rfc6749#section-4.1)，该授予交换授权码以获取令牌。

附加参数：

* `code` - 是从授权服务器接收的授权码。
* `grant_type` - 是一个参数，解释授予类型是什么，以及将返回哪个令牌。
* `client_secret` - 是仅应用程序和授权服务器知道的秘密。

#### 它是如何工作的

![](/files/gpEqZCUFFqK2JqDbYeCZ)

1. 用户在常规Web应用程序中单击 `Login`。
2. 应用程序将用户重定向到登录和授权提示（另请参阅[授权请求的附加信息](https://tools.ietf.org/html/rfc6749#section-4.1.1)）。请求可能看起来像这样：

   ```http
   https://auth-server.com/auth?
       response_type=code&
       client_id=application_client_id&
       redirect_uri=https://application-website.com/callback&
       scope=read:email&
       state=kYlr93jbdhyguIVF73moq
   ```
3. 用户使用配置的登录选项之一进行身份验证，可能会看到列出授权服务器将授予常规Web应用程序的权限的同意页面。同意页面和请求可能看起来像这样：

   ![](/files/JJ8x1dwbQrGj5I2ZKz05)

   ```http
   POST /auth HTTP/1.1
   Host: auth-server.com
   Content-Type: application/json
   Cookie: session_id=Ja2upepCYz5IhSdSIUn1lyi2Ylir3afn

   {
       "client_id": "application_client_id",
       "redirect_uri": "https://application-website.com/callback",
       "state": "kYlr93jbdhyguIVF73moq",
       "response_type": "code",
       "scope": "read:email"
   }
   ```
4. 一旦接受，授权服务器将向 `redirect_uri` 发送带有 `code` 和 `state` 参数的请求（另请参阅[授权响应的附加信息](https://tools.ietf.org/html/rfc6749#section-4.1.2)）：

   ```http
   https://application-website.com/callback?
       code=a68YhewbiYl93TR89hdjYwqP0&
       state=kYlr93jbdhyguIVF73moq
   ```
5. 应用程序使用接收到的 `code` 以及自己的 `client_id` 和 `client_secret` 将请求 `access_token`（另请参阅[访问令牌请求的附加信息](https://tools.ietf.org/html/rfc6749#section-4.1.3)）：

   ```http
   POST /oauth/access_token HTTP/1.1
   Host: auth-server.com
   Contetn-Type: application/json
   Content-Length: 157

   {
       "client_id": "application_client_id",
       "client_secret": "application_client_secret",
       "grant_type": "authorization_code",
       "code": "a68YhewbiYl93TR89hdjYwqP0"
   }
   ```
6. 最后，流程完成，应用程序将使用 `access_token` 向资源服务器发出API调用以访问用户的数据（另请参阅[访问令牌响应的附加信息](https://tools.ietf.org/html/rfc6749#section-4.1.4)）。

### 隐式授予

[隐式授予](https://tools.ietf.org/html/rfc6749#section-4.2) 简单得多。客户端应用程序不是首先获取授权码然后交换访问令牌，而是在用户给予同意后立即接收访问码。

#### 它是如何工作的

1. 用户在常规Web应用程序中单击 `Login`
2. 应用程序将用户重定向到登录和授权提示（另请参阅[授权请求的附加信息](https://tools.ietf.org/html/rfc6749#section-4.2.1)）。请求可能看起来像这样：

   ```http
   https://auth-server.com/auth?
       response_type=token&
       client_id=application_client_id&
       redirect_uri=https://application-website.com/callback&
       scope=read:email&
       state=kYlr93jbdhyguIVF73moq
   ```
3. 用户进行身份验证并决定是否同意请求的权限。此过程与授权码授予完全相同。
4. 一旦接受，授权服务器将用户重定向到授权请求中指定的 `redirect_uri`。但是，它不会发送包含授权码的查询参数，而是将访问令牌和其他特定于令牌的数据作为URL片段发送（另请参阅[访问令牌响应的附加信息](https://tools.ietf.org/html/rfc6749#section-4.2.2)）：

   ```http
   https://application-website.com/callback?
       access_token=z0y9x8w7v6u5&
       token_type=Bearer&
       expires_in=5000&
       scope=email:read&
       state=kYlr93jbdhyguIVF73moq
   ```
5. 一旦客户端应用程序成功从URL片段中提取 `access_token`，它就可以使用它访问用户的数据。

## 客户端应用程序中的安全问题

### 隐式授予的不当实现

在隐式授予中，`access_token` 通过用户的浏览器从授权服务发送到客户端应用程序作为URL片段。客户端应用程序然后使用javascript访问令牌。问题是，如果应用程序希望在用户关闭页面后维护会话，它需要将当前用户数据（通常是 `user_id` 和 `access_token`）存储在某个地方。

为了解决这个问题，客户端应用程序通常会将此数据在POST请求中提交到服务器，然后为用户分配会话cookie，有效地登录他们。此请求大致相当于可能作为基于经典密码登录的一部分发送的表单提交请求。但是，在这种情况下，服务器没有任何秘密或密码可以与提交的数据进行比较，这意味着它是隐式信任的。

因此，如果客户端应用程序没有正确检查 `access_token` 是否与请求中的其他数据匹配，这种行为可能导致严重的漏洞。在这种情况下，攻击者可以简单地更改发送到服务器的参数以冒充任何用户。

### state参数的不当处理

如果 `state` 参数是：

* 缺失的，
* 永不改变的静态值，
* 存在但未经验证的，

OAuth 2.0流程可能容易受到CSRF攻击。

要利用这一点，请在您的账户下完成授权过程并在授权后立即暂停。然后将此URL发送给已登录的受害者，受害者会将您的账户添加到拥有。

### 秘密泄露

私有OAuth 2.0参数可能泄露。例如，如果在授权码授予中为 `access_token` 的请求（步骤5）是从客户端而不是服务器执行的。

### 开放重定向

如果应用程序在错误的情况下将用户重定向到 `redirect_uri`，例如由于不正确的scope值，则可能发生开放重定向。在这种情况下，攻击者制作以下链接：

```http
https://vulnerable-website.com/authorize?
    response_type=code&
    client_id=abcdef123456&
    scope=WRONG_SCOPE&
    redirect_uri=https://attacker-website.com
```

应用程序在处理请求时，将受害者重定向到攻击者的主机。

{% embed url="<http://blog.intothesymmetry.com/2015/04/open-redirect-in-rfc6749-aka-oauth-20.html>" %}

## 授权服务器中的安全问题

### 滥用API

如果 `access_token` 允许向API发出请求，则值得检查是否可以滥用这一点。有时授予 `access_token` 的权利允许您发布具有增加权限的新令牌或获取仅与会话令牌一起使用的额外功能的访问权限。

### 滥用未确认电子邮件的账户

授权服务器可以允许使用未确认电子邮件的账户来授予对受保护资源的访问权限。因此，第三方应用程序将信任接收到的数据。

{% embed url="<https://gitlab.com/gitlab-org/gitlab/-/issues/37038>" %}

### 基于电子邮件地址的账户分配

应用程序通常允许多种身份验证方法：使用密码登录和使用第三方服务如github或google。有几种攻击方法：

* 如果应用程序在账户创建时不需要电子邮件验证，请尝试使用受害者的电子邮件地址创建账户。有时，当受害者然后尝试使用第三方服务注册或登录时，应用程序执行搜索，看到电子邮件已经注册并将第三方服务账户与攻击者创建的账户关联。在这种情况下，攻击者将能够访问受害者的账户。
* 如果第三方服务在账户创建时不需要电子邮件验证，请尝试使用受害者的电子邮件地址创建账户。可能存在相同的问题，但您将从另一个方向攻击并获取对受害者账户的访问权限以进行账户接管。

### 通过批量分配绕过redirect\_uri验证

{% embed url="<https://nvd.nist.gov/vuln/detail/CVE-2021-27582>" %}

### CSRF授权服务器

doorkeeper中的CSRF漏洞允许攻击者将他们的应用程序静默安装到DigitalOcean上的受害者并将 `access_token` 传输到受控服务器。攻击详情可以查看[此处](https://habr.com/ru/post/246025/)（俄语）或[此处](http://homakov.blogspot.com/2014/12/blatant-csrf-in-doorkeeper-most-popular.html)。

### Host头中毒

毒化 `Host` 头不仅可以在密码恢复期间导致账户接管，还可以在OAuth身份验证期间导致账户接管。有时您可以通过毒化 `Host` 头影响 `redirect_uri`。结果，当受害者交换授权码以获取访问令牌时，他/她将带有此令牌的请求发送到您的域。易受攻击的请求示例：

```http
GET /twitter/login HTTP/1.1
Host: attacker-website.com/vulnerable-website.com
```

参考资料：

* [Write up: Account Takeover in Periscope TV](https://hackerone.com/reports/317476)

### acr或amr的错误配置

授权服务器可能接受 `acr_values` 或 `amr_values` 参数并使用它们来处理身份验证请求。

`amr_values`（或身份验证方法参考值）指定身份验证中使用的身份验证方法。例如，值可能表示使用了密码和OTP身份验证方法。

`acr_values`（或请求的身份验证上下文类参考值）指定身份验证被请求满足的一组业务规则。这些规则通常可以通过使用许多不同的特定身份验证方法来满足，单独或组合使用。

换句话说，您可以通过这些参数设置身份验证方法。如果配置不正确，可能导致绕过身份验证的可能性。例如，如果客户端发送 `amr_values=pwd+otp`，您可以通过传递 `amr_values=pwd` 尝试绕过双因素身份验证。

参考资料：

* [RFC8176: Authentication Method Reference Values](https://datatracker.ietf.org/doc/html/rfc8176)
* [使用OpenID错误配置绕过2FA](https://youst.in/posts/bypassing-2fa-using-openid-misconfiguration/)

### redirect\_uri会话中毒

如果授权请求（授权码流程中的步骤3）不包含任何关于被授权客户端的参数，授权服务器可能从用户的会话中获取它们。在这种情况下，授权请求可能看起来像这样：

```http
POST /auth HTTP/1.1
Host: auth-server.com
Content-Type: application/json
Cookie: session_id=Ja2upepCYz5IhSdSIUn1lyi2Ylir3afn

{
    "scope": "read:email",
    "authorize": "Authorize"
}
```

基于此行为的攻击可能看起来像这样：

1. 受害者访问特制页面（就像典型的XSS/CSRF攻击场景）。
2. 页面将用户重定向到OAuth授权页面，带有"受信任的" `client_id`。
3. 页面向OAuth授权页面发送隐藏的跨域请求，带有"不受信任的" `client_id`，这会毒化会话。
4. 受害者批准第一个页面，并且由于会话包含更新后的值，受害者将被重定向到"不受信任"客户端的 `redirect_uri`。

在许多真实系统中，第三方用户可以注册自己的客户端，因此此漏洞可能允许他们注册任意的 `redirect_uri` 并将令牌泄露给它。

如果用户之前已批准客户端，服务器可能只是重定向用户而不要求确认。OpenID Connect规范提供了 `prompt=consent` 参数，您可以将其附加到授权请求的URL以潜在绕过此问题。如果服务器遵循OpenID Connection规范，即使用户之前已授予同意，它也应该要求用户确认其同意。没有确认，利用更难但仍然可能，取决于特定的OAuth服务器实现。

### Scope升级：授权码授予

使用授权码授予类型，用户的数据通过安全的服务器到服务器通信请求和发送，第三方攻击者通常无法直接操纵。但是，通过向授权服务注册自己的客户端应用程序，仍可能实现相同的结果。

例如，假设攻击者的恶意客户端应用程序最初使用 `read:email` scope请求访问用户的电子邮件地址。在用户批准此请求后，恶意客户端应用程序接收授权码。由于攻击者控制其客户端应用程序，他们可以向代码交换请求添加另一个scope参数，包含额外的profile scope：

```http
POST /oauth/access_token HTTP/1.1
Host: auth-server.com
Contetn-Type: application/json
Content-Length: 191

{
    "client_id": "application_client_id",
    "client_secret": "application_client_secret",
    "grant_type": "authorization_code",
    "code": "a68YhewbiYl93TR89hdjYwqP0",
    "scope": "read:email%20read:user"
}
```

如果服务器没有根据初始授权请求的scope验证这一点，它将使用新的scope生成 `access_token` 并将其发送到攻击者的客户端应用程序。

### Scope升级：隐式授予

对于隐式授予类型，`access_token` 通过浏览器发送，这意味着攻击者可以窃取与无辜客户端应用程序关联的令牌并直接使用它们。一旦他们窃取了 `access_token`，他们可以向授权服务的端点发送正常的基于浏览器的请求，在此过程中手动添加新的scope参数。

### Scope升级：滥用重新发布令牌

应用程序通常支持令牌重新发布功能。有几种攻击方法：

* 尝试在令牌重新发布请求中显式传递scope。有时这允许您为新令牌更改scope。
* 如果应用程序可以充当OAuth 2.0提供程序并且您可以管理scope，请尝试创建令牌，更改scope并重新发布它们。有时新令牌的scope会更改。

### 弱redirect\_uri配置

`redirect_uri` 非常重要，因为敏感数据如 `code` 在授权后附加到此URL。如果 `redirect_uri` 可以重定向到攻击者控制的服务器，这意味着攻击者可以潜在地通过自己使用代码接管受害者的账户。

根据服务器处理的逻辑，有几种弱配置：

* 开放重定向：

  ```http
  https://auth-server.com/auth?
      redirect_uri=https://attacker-website.com
  ```
* 路径遍历或通过代理页面窃取令牌：

  ```http
  https://auth-server.com/auth?
      redirect_url=https://application-website.com/callback/../some/path
  ```
* 弱正则表达式：

  ```http
  https://auth-server.com/auth?
      redirect_url=https://application-website.com.attacker-website.com
  ```
* 不同组件对URI解析的差异：

  ```http
  https://auth-server.com/auth?
      redirect_url=https://application-website.com%20%26@foo.attacker-website.com#@bar.attacker-website.com
  ```
* HTTP参数污染：

  ```http
  https://auth-server.com/auth?
      redirect_uri=https://application-website.com/callback&
      redirect_uri=https://attacker-website.com
  ```
* 在生产环境中意外允许任何以 `localhost` 开头的重定向URI：

  ```http
  https://auth-server.com/auth?
      redirect_uri=https://localhost.attacker-website.com
  ```
* HTML注入和通过 `Referer` 头窃取令牌：

  ```html
  <img src="attacker-website.com">
  ```
* 处理查询参数和URL片段的危险JavaScript。

参考资料：

* [Report: Stealing SSO Login Tokens (snappublisher.snapchat.com)](https://hackerone.com/reports/265943)
* [Write up: OAuth and PostMessage](https://ninetyn1ne.github.io/2022-02-21-oauth-postmessage-misconfig/)

## 参考资料

* [OAuth 2.0授权框架](https://auth0.com/docs/protocols/oauth2)
* [Top X OAuth 2 Hacks (OAuth Implementation vulnerabilities)](https://example.com/oauth-hacks.pdf)
* [Web Security Academy: OAuth 2.0 authentication vulnerabilities](https://portswigger.net/web-security/oauth)
* [PortSwigger Articles: Hidden OAuth attack vectors](https://portswigger.net/research/hidden-oauth-attack-vectors)
* [Write up: Bypassing GitHub's OAuth flow](https://blog.teddykatz.com/2019/11/05/github-oauth-bypass.html)
* [Report: Stealing users Bitbucket app tokens to retrieve sensitive data from connected Bitbucket accounts](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/56500)
* [Report: Chained Bugs to Leak Victim's Uber's FB Oauth Token](https://hackerone.com/reports/202781)
* [Write up: Facebook OAuth Framework Vulnerability](https://www.amolbaikar.com/facebook-oauth-framework-vulnerability/)
* [Write up: How I hacked Github again](http://homakov.blogspot.com/2014/02/how-i-hacked-github-again.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gitbook.cdxiaodong.life/web-ying-yong-an-quan/oauth-2.0-vulnerabilities.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
