OpenID Connect漏洞

OpenID Connect

OpenID Connect 是OAuth 2.0协议顶部的简单身份层。它使客户端能够基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似REST的方式获取关于最终用户的基本配置文件信息。

OpenID Connect实现身份验证作为OAuth 2.0授权过程的扩展。通过在授权请求中包含 openid scope值来请求使用此扩展。关于所执行身份验证的信息在称为 ID token 的JSON Web Token(JWT)中返回。

OpenID Connect角色

在任何OpenID Connect流程中,我们可以识别以下角色:

  • End-User - 人类参与者。

  • Relying Party (client) - 需要最终用户身份验证和来自OpenID Provider的声明的OAuth 2.0客户端应用程序。

  • OpenID Provider - 能够验证最终用户并向依赖方提供关于身份验证事件和最终用户的声明的OAuth 2.0授权服务器。

协议流程

抽象地说,OpenID Connect协议具有以下流程:

  1. 依赖方(客户端)向OpenID Provider发送请求。

  2. OpenID Provider验证最终用户并获得授权。

  3. OpenID Provider以ID令牌和通常是访问令牌响应。

  4. 依赖方(客户端)可以使用访问令牌向UserInfo端点发送请求。

  5. UserInfo端点返回关于最终用户的声明。

身份验证

OpenID Connect执行身份验证以登录最终用户或确定最终用户已经登录。OpenID Connect以安全的方式将服务器执行的身份验证结果返回给客户端,以便客户端可以依赖它。因此,在这种情况下,客户端称为依赖方。

身份验证可以遵循以下三条路径之一:

ID令牌

OpenID Connect对OAuth 2.0的主要扩展以启用最终用户身份验证是ID令牌数据结构。ID令牌是包含关于授权服务器使用客户端对最终用户进行身份验证的声明的安全令牌,以及可能的其他请求声明。ID令牌表示为使用JSON Web签名(JWS)签名的JSON Web令牌(JWT)。

以下声明在OpenID Connect使用的所有OAuth 2.0流程中的ID令牌内使用(ID令牌也可能包含其他声明):

声明
可选性
描述

iss

必需

响应发布者的发布者标识符。iss值是使用 https 方案的不区分大小写的URL,包含方案、主机,以及可选的端口号和路径组件,没有查询或片段组件。

sub

必需

主题标识符。sub值是不区分大小写的ASCII字符串,长度不超过255个字符。在发布者内为最终用户的本地唯一且永不重新分配的标识符,旨在由客户端使用。

aud

必需

此ID令牌针对的受众。aud值是不区分大小写字符串的数组(或当只有一个受众时为单个不区分大小写的字符串)。它包含依赖方的OAuth 2.0 client_id,也可能包含其他受众的标识符。

exp

必需

过期时间。

iat

必需

JWT的签发时间。

auth_time

最终用户身份验证发生的时间。

nonce

用于将客户端会话与ID令牌关联的字符串值,并减轻重放攻击。该值未经修改地从身份验证请求传递到ID令牌。

acr

可选

身份验证上下文类引用,请查看此处

amr

可选

身份验证方法引用。身份验证中使用的方法标识符的字符串JSON数组。例如,值可能表示使用了密码和OTP身份验证方法。

azp

可选

授权方 - ID令牌被发布的方,请查看此处

授权码流程

使用授权码流程时,以下ID令牌声明的附加要求适用:

声明
可选性
描述

at_hash

可选

访问令牌哈希值。

隐式流程

使用隐式流程时,以下ID令牌声明的附加要求适用:

声明
可选性
描述

nonce

必需

此流程需要使用nonce声明。

at_hash

必需

访问令牌哈希值;当未颁发访问令牌时可能不使用,这是 response_type=id_token 的情况。

混合流程

使用混合流程时,以下ID令牌声明的附加要求适用:

声明
可选性
描述

nonce

必需

此流程需要使用nonce声明。

at_hash

必需

访问令牌哈希值;当未颁发访问令牌时可能不使用。

c_hash

代码哈希值;当未颁发代码时可能不使用。

OpenID Connect端点

OpenID Connect利用三个端点:

  • 授权端点

  • 令牌端点

  • UserInfo端点

授权端点

授权端点执行最终用户的身份验证。这是通过将用户代理发送到授权服务器的授权端点进行身份验证和授权,使用OAuth 2.0定义的请求参数和OpenID Connect定义的附加参数和参数值来完成的。

令牌端点与每个授权流程一起使用。

OpenID Connect使用以下OAuth 2.0请求参数:

声明
可选性
描述

scope

必需

访问请求的scope。OpenID Connect请求必须包含 openid scope值。

response_type

必需

OAuth 2.0响应类型值,确定要使用的授权处理流程,包括从使用的端点返回什么参数。

client_id

必需

在授权服务器有效的OAuth 2.0客户端标识符。

redirect_uri

必需

响应将被发送到的重定向URI。

state

推荐

用于在请求和回调之间保持状态的不透明值。通常,通过将此参数的值与浏览器cookie加密绑定来完成跨站点请求伪造缓解。

OpenID Connect还使用附加的请求参数:

声明
可选性
描述

nonce

可选

用于将客户端会话与ID令牌关联的字符串值,并减轻重放攻击。

display

可选

指定授权服务器如何向最终用户显示身份验证和同意用户界面页面的ASCII字符串值,请查看此处

prompt

可选

不区分大小写的ASCII字符串值的空间分隔列表,指定授权服务器是否提示最终用户重新身份验证和同意,请查看此处

max_age

可选

最大身份验证年龄。指定自上次OpenID Provider主动验证最终用户以来允许的经过时间(以秒为单位)。

ui_locales

可选

用户界面的最终用户首选语言和脚本,请查看此处

id_token_hint

可选

以前由授权服务器颁发的ID令牌,作为关于最终用户的当前或过去身份验证会话的提示传递,请查看此处

login_hint

可选

关于最终用户可能用于登录的登录标识符的授权服务器提示,请查看此处

acr_values

可选

请求的身份验证上下文类引用值,请查看此处

response_type如何工作

response_type 请求参数用于通知授权服务器使用哪种流程类型:

  • code - 使用授权码流程的值。

  • tokenid_token token - 使用隐式流程的值。

  • code id_tokencode tokencode id_token token - 使用混合流程的值。

nonce如何工作

nonce 声明用于将客户端会话与ID令牌关联,并减轻重放攻击。该值未经修改地从身份验证请求传递到ID令牌。如果ID令牌包含 nonce 声明,客户端验证此值等于在身份验证请求中发送的 nonce 参数的值。通常,它根据以下流程工作:

  1. 客户端生成安全的随机值并将其原样存储在HttpOnly会话cookie中。

  2. 客户端使用该值的加密哈希作为nonce参数并将其作为身份验证请求参数发送。

  3. 当ID令牌包含nonce声明时,客户端从持久存储(在这种情况下从cookie)中提取并删除随机值,哈希此值,并与ID令牌中的nonce声明进行比较。如果它们不匹配,客户端拒绝建立身份。

另请参阅 Nonce Implementation Notes

令牌端点

令牌端点由依赖方(客户端)用于获取访问令牌、ID令牌,以及可选的刷新令牌。令牌端点需要依赖方(客户端)身份验证并支持几种方法,详细信息您可以找到此处

令牌端点由授权码和混合流程类型使用。

使用混合流程时,从令牌端点返回的ID令牌的内容与从授权端点返回的ID令牌相同。

UserInfo端点

UserInfo端点是OAuth 2.0受保护的资源,返回关于已验证最终用户的声明。要获取关于最终用户的请求声明,客户端使用通过OpenID connect身份验证获得的访问令牌向UserInfo端点发出请求。这些声明通常由包含声明的名称和值对的JSON对象表示。您可以在此查看标准的基本配置文件声明集此处

授权码流程

授权码流程 将授权码返回给客户端,然后客户端可以将其交换为ID令牌和访问令牌。

它是如何工作的

  1. 客户端准备包含所需请求参数的身份验证请求并将请求发送到授权服务器(另请参阅授权服务器如何验证身份验证请求)。请求可能看起来像这样:

    https://auth-server.com/auth?
        response_type=code&
        scope=openid%20profile%20email&
        client_id=s6BhdRkqt3&
        state=af0ifjsldkj&
        redirect_uri=https://application-website.com/callback
  2. 授权服务器将最终用户发送回客户端,带有授权码:

    https://application-website.com/callback?
        code=SplxlOBeZQQYbYS6WxSbIA&
        state=af0ifjsldkj
  3. 客户端在令牌端点使用授权码请求响应(另请参阅关于客户端身份验证授权服务器如何验证令牌请求的附加信息)。请求可能看起来像这样:

    POST /token HTTP/1.1
    Host: auth-server.com
    Content-Type: application/json
    Content-Length: 185
    
    {
        "client_id": "s6BhdRkqt3",
        "client_secret": "gX1fBat3bV",
        "grant_type": "authorization_code",
        "code": "SplxlOBeZQQYbYS6WxSbIA",
        "redirect_uri": "https://application-website.com/callback"
    }
  4. 客户端接收包含ID令牌和访问令牌的响应,验证响应并检索最终用户的主题标识符:

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 475
    Cache-Control: no-store
    Pragma: no-cache
    
    {
        "access_token": "SlAV32hkKG",
        "token_type": "Bearer",
        "refresh_token": "8xLOxBtZp8",
        "expires_in": 3600,
        "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.
                    eyJpc3MiOiJodHRwczovL2F1dGgtc2VydmVyLmNvbSIs
                    InN1YiI6IjI0ODI4OTc2MTAwMSIsImF1ZCI6InM2Qmhk
                    UmtxdDMiLCJleHAiOjEzMTEyODE5NzAsImlhdCI6MTMx
                    MTI4MDk3MH0.
                    R06v-GNzsQiT8bc4DbkCm6laQ37fHcbLST4Pf52Yr6Jn
                    36DCP-TaWY6cyko6SyqmfPqqEJ3M9x5AtievsvM-CEGt
                    ggBP7-PeuicHS16199b6syKyQFfkUT8jFj_NcdVc-VL_
                    Zj6g_bgcjRpB1KLgKqqvA3hDBWhI2NHYeCI7eRY"
    }

隐式流程

隐式流程 简单得多。客户端应用程序不是首先获取授权码然后交换访问令牌,而是在用户给予同意后立即接收访问码。

它是如何工作的

  1. 客户端准备包含所需请求参数的身份验证请求并将请求发送到授权服务器(另请参阅授权服务器如何验证身份验证请求)。请求可能看起来像这样:

    https://auth-server.com/auth?
        response_type=id_token%20token
        client_id=s6BhdRkqt3&
        redirect_uri=https://application-website.com/callback&
        scope=openid%20profile&
        state=af0ifjsldkj&
        nonce=n-0S6_WzA2Mj
  2. 授权服务器将最终用户发送回客户端,带有ID令牌和如果请求的访问令牌;客户端验证身份验证响应并检索最终用户的主题标识符:

    https://application-website.com/callback?
        access_token=SlAV32hkKG&
        token_type=bearer&
        id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso&
        expires_in=3600&
        state=af0ifjsldkj

混合流程

混合流程 从授权端点返回一些令牌,从令牌端点返回其他令牌。

它是如何工作的

  1. 客户端准备包含所需请求参数的身份验证请求并将请求发送到授权服务器(另请参阅授权服务器如何验证身份验证请求)。请求可能看起来像这样:

    https://auth-server.com/auth?
        response_type=code%20id_token&
        client_id=s6BhdRkqt3&
        redirect_uri=https://application-website.com/callback&
        scope=openid%20profile%20email&
        nonce=n-0S6_WzA2Mj&
        state=af0ifjsldkj
  2. 授权服务器将最终用户发送回客户端,带有授权码和根据响应类型的一个或多个附加参数:

    https://application-website.com/callback?
        code=SplxlOBeZQQYbYS6WxSbIA&
        id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso&
        state=af0ifjsldkj
  3. 客户端在令牌端点使用授权码请求响应(另请参阅关于客户端身份验证授权服务器如何验证令牌请求的附加信息)。请求可能看起来像这样:

    POST /token HTTP/1.1
    Host: auth-server.com
    Content-Type: application/json
    Content-Length: 185
    
    {
        "client_id": "s6BhdRkqt3",
        "client_secret": "gX1fBat3bV",
        "grant_type": "authorization_code",
        "code": "SplxlOBeZQQYbYS6WxSbIA",
        "redirect_uri": "https://application-website.com/callback"
    }
  4. 客户端接收包含ID令牌和访问令牌的响应,验证响应并检索最终用户的主题标识符。

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 475
    Cache-Control: no-store
    Pragma: no-cache
    
    {
        "access_token": "SlAV32hkKG",
        "token_type": "Bearer",
        "refresh_token": "8xLOxBtZp8",
        "expires_in": 3600,
        "id_token": "eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso"
    }

OAuth2.0协议中的安全问题

JSON Web Token的安全问题

依赖方中的安全问题

nonce声明的不当处理

如果 nonce 声明是:

  • 缺失的,

  • 永不改变的静态值,

  • 存在但未经验证的,

  • 在客户端以明文对攻击者可用,

OpenID Connect流程可能容易受到重放攻击。要利用这一点,您需要获取身份验证响应并使用它来建立身份。

OpenID提供者中的安全问题

尝试发现 .well-known/openid-configuration 端点,根据规范它包含关于不同服务器配置值的有用信息。

滥用WebFinger服务

为了使OpenID Connect依赖方能够为最终用户利用OpenID Connect服务,依赖方需要知道OpenID提供者在哪里。OpenID Connect使用WebFinger来定位最终用户的OpenID提供者。详细信息您可以在规范中找到。

WebFinger服务在 .well-known/webfinger 端点可用,允许您获取关于服务器上使用的用户和资源的信息。它可用于验证"匿名"用户在服务器上是否有账户:

https://openid-server.com/.well-known/webfinger?
    resource=http://x/anonymous&
    rel=http://openid.net/specs/connect/1.0/issuer
HTTP/1.1 200
Content-Type: application/json
Content-Length: 132

{
    "subject": "http://x/anonymous",
    "links": [
        "rel": "http://openid.net/specs/connect/1.0/issuer",
        "href": "https://auth-server.com/auth"
    ]
}

rel 参数应该具有 http://openid.net/specs/connect/1.0/issuer 的静态值。

resource 应该包含以下形式之一的有效URL:

  • http(s)://host/user

  • acct://user@host

UserInfo端点:CSP错误配置

UserInfo端点应支持使用根据规范的跨源资源共享。

通过动态客户端注册进行SSRF

OpenID Connect Dynamic Client Registration 规范定义了允许客户端应用程序向OpenID提供者注册的标准化方式。如果支持动态客户端注册,客户端应用程序可以通过向 /register 端点发送 POST 请求来注册自己。注册请求可能看起来像这样:

POST /connect/register HTTP/1.1
Host: auth-server.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJ...

{
    "application_type": "web",
    "redirect_uris":["https://application-website.com/callback"],
    "client_name": "My Example",
    "logo_uri": "https://application-website.com/logo.png",
    "subject_type": "pairwise",
    "sector_identifier_uri": "https://other-application-website.com/file_of_redirect_uris.json",
    "token_endpoint_auth_method": "client_secret_basic",
    "jwks_uri": "https://application-website.com/my_public_keys.jwks",
    "userinfo_encrypted_response_alg": "RSA1_5",
    "userinfo_encrypted_response_enc": "A128CBC-HS256",
    "contacts": ["[email protected]"],
    "request_uris":["https://application-website.com/rf.txt"]
}

其中一些值通过URL链接传入,可能潜在地容易受到SSRF攻击。

大多数服务器在接收到注册请求时不会立即解析这些URL。相反,它们只是保存这些参数并在OAuth授权流程期间稍后使用它们。

以下参数对SSRF攻击特别有趣:

  • logo_uri - 引用客户端应用程序logo的URL。在注册客户端后,您可以尝试使用新的"client_id"调用OAuth授权端点。登录后,服务器将要求您批准请求并可能显示来自"logo_uri"的图像。如果服务器自己获取图像,则此步骤应触发SSRF。或者,服务器可能只是通过客户端侧 <img> 标签包含logo,这可能导致XSS。

  • jwks_uri - 客户端的JSON Web Key Set文档的URL。服务器上需要此密钥集以验证在使用JWT进行客户端身份验证时向令牌端点发出的签名请求。为了测试此参数中的SSRF,注册一个带有恶意"jwks_uri"的新客户端应用程序,执行授权过程以获取任何用户的授权码,然后获取带有如下请求体的令牌端点。如果易受攻击,服务器应该对提供的"jwks_uri"执行服务器到服务器的HTTP请求,因为它需要此密钥来检查请求中"client_assertion"参数的有效性。尽管如此,这可能只是一个盲SSRF漏洞,因为服务器期望正确的JSON响应。

    POST /token HTTP/1.1
    Host: auth-server.com
    Content-Type: application/json
    
    {
        "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        "client_assertion": "eyJhbGci...",
        "grant_type": "authorization_code",
        "code": "SplxlOBeZQQYbYS6WxSbIA"
    }    
  • sector_identifier_uri - 此URL引用具有 redirect_uri 值的单个JSON数组的文件。如果支持,服务器可能在您提交动态注册请求后立即获取此值。如果没有立即获取,尝试在服务器上为此客户端执行授权。由于它需要知道 redirect_uris 以完成授权流程,这将强制服务器向您的恶意 sector_identifier_uri 发出请求。

  • request_uris - 此客户端允许的 request_uris 的数组。request_uri 参数可能在授权端点上受支持,以提供包含请求信息的JWT的URL,另请参阅此处。要利用授权端点上的SSRF,只需使用 request_uri

    https://auth-server.com/auth?
        response_type=code%20id_token&
        client_id=s6BhdRkqt3&
        request_uri=https://attacker-website.com/request.jwt

以下参数也包含URL,但通常不用于发出服务器到服务器的请求。它们用于客户端侧重定向/引用:

  • redirect_uri - 用于在授权后重定向客户端的URL

  • client_uri - 客户端应用程序主页的URL

  • policy_uri - 依赖方客户端应用程序提供的URL,以便最终用户可以阅读他们的配置文件数据将如何被使用。

  • tos_uri - 依赖方提供的URL,以便最终用户可以阅读依赖方的服务条款。

  • initiate_login_uri - 使用https方案的URI,第三方可以使用它来启动依赖方的登录。也应用于客户端侧重定向。

所有参数及其定义您可以找到此处

通过request_uri参数进行SSRF

request_uri 参数在授权过程开始时由服务器获取(不要将此参数与 redirect_uri 混淆)。

许多服务器不允许任意的 request_uri 值:它们只允许在客户端注册过程中预先注册的允许URL列表。但即使没有启用动态客户端注册,或者它需要身份验证,您也可以尝试通过使用 request_uri 绕过验证并在授权端点上执行SSRF:

https://auth-server.com/auth?
    response_type=code%20id_token&
    client_id=s6BhdRkqt3&
    request_uri=https://attacker-website.com/request.jwt

参考资料

最后更新于