什么是OAuth?
OAuth是一个得到广泛应用的关于授权(authorization)的开放网络标准,目前版本为2.0。
在传统的用户-服务器授权认证模型中,用户要获取服务器上受保护的某些资源只需向对应服务器进行认证;然而如果有第三方应用想要获取用户在服务器上的访问受限资源,就需要第三方应用获取用户授权后再向服务器申请访问此访问受限资源。
OAuth解决的就是这一授权问题,它的基本思想是在第三方应用与服务提供商之间设置一个授权层,第三方应用从服务商获取用户的访问受限资源使用的验证手段不是用户的登录口令,而是用户通过授权层授予第三方应用的访问令牌。
其中授权层由服务提供商来进行实现与维护。这种访问令牌的思想有点类似于kerberos协议。
本文首先明确OAuth协议中所涉及的几个角色:
- Third-party application:第三方应用程序,本文中又称"客户端”(client)。
- HTTP service:HTTP服务提供商,本文中简称“服务提供商”。
- Resource Owner:资源所有者,本文中又称"用户”(user)。
- User Agent:用户代理,本文中就是指浏览器。
- Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
客户端要得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
授权码模式
授权码模式功能最完整、流程最严密,它的认证流程如下图所示:
步骤说明如下:
- (A) 用户访问客户端,后者将前者导向认证服务器。
- (B) 用户选择是否给予客户端授权。
- (C) 假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI”(redirection URI),同时附上一个授权码。
- (D) 客户端收到授权码,附上早先的"重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- (E) 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为"code”
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
C步骤中,服务器回应客户端的URI,包含以下参数:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code”。
- code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- client_id:表示客户端ID,必选项。
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
从以上步骤可以看出,这种模式下,认证服务器对用户和客户端都进行了认证。B到C步骤之间认证服务器完成了对用户身份的认证并记录了发起此申请的客户端,D到E步骤之间在客户端使用授权码申请令牌时则完成了对客户端身份的认证。
此外,在上述参数中,redirect_uri用来在C步骤中将用户重定向至此URI来告知客户端授权码;而state参数在此流程中没有具体作用,但在实际应用中,可以被客户端使用来进行防止CSRF攻击。
简化模式
简化模式的获取访问令牌的授权过程不经过第三方应用的服务器,而是由浏览器完成获取令牌的步骤并发送给第三方应用。在此模式中,客户端并不需要验证。其流程如下:
步骤说明如下:
- (A)客户端将用户导向认证服务器。
- (B)用户决定是否给于客户端授权。
- (C)假设用户给予授权,认证服务器将用户导向客户端指定的“重定向URI”,并在URI的Hash部分包含了访问令牌。
- (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
- (E)资源服务器返回一个脚本,其中包含的代码可以获取Hash值中的令牌。
- (F)浏览器执行上一步获得的脚本,提取出令牌。
- (G)浏览器将令牌发给客户端。
A步骤中,客户端发出的HTTP请求,包含以下参数:
- response_type:表示授权类型,此处的值固定为"token”,必选项。
- client_id:表示客户端的ID,必选项。
- redirect_uri:表示重定向的URI,可选项。
- scope:表示权限范围,可选项。
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
C步骤中,认证服务器回应客户端的URI,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
密码模式
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向“服务商提供商”索要授权。
这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。其流程如下:
步骤说明如下:
- (A)用户向客户端提供用户名和密码。
- (B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
- (C)认证服务器确认无误后,向客户端提供访问令牌。
B步骤中,客户端发出的HTTP请求,包含以下参数:
- grant_type:表示授权类型,此处的值固定为"password”,必选项。
- username:表示用户名,必选项。
- password:表示用户的密码,必选项。
- scope:表示权限范围,可选项。
客户端模式
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。其流程如下:
步骤说明如下:
- (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
- (B)认证服务器确认无误后,向客户端提供访问令牌。
安全问题整理
OAuth协议本身只是一种规范标准,如果严格按照协议的标准进行应用间的认证授权基本是不会出现安全问题的。但在平台方与第三方实际应用协议时,往往会由于实现的疏漏而导致漏洞的产生,尤其是第三方的程序员水平参差不齐,如果对OAuth协议理解有偏差的话,会导致授权认证过程中的诸多问题。比如OAuth被广泛使用在第三方登录的应用场景,这其中如果存在漏洞,会导致未授权登录等严重问题。
本文对OAuth使用过程中可能出现的常见问题进行了整理。
使用错误的OAuth信息用于认证交换
在利用开放平台账号进行第三方应用登录的过程中,在用户进行完授权后,第三方应用会获得由开放平台返回的凭据,如简化模式中第三方应用能够通过redirect_uri直接获得access_token,而且有的开放平台同时会在参数中返回其他的信息如用户的uid等。
如果第三方应用直接使用uid信息进行用户登录的操作,而没有使用access_token来获取用户信息,就会导致安全问题。因为重定向的redirect_uri是可以被用户截获修改的,其中的参数可能被用户恶意修改。漏洞示例:
无绑定token
在OAuth流程中,用户的浏览器跳转至指定的redirect_uri以向第三方应用传递access_token等参数时,用户可以对此链接中的参数进行修改。如果攻击者可以将access_token参数更改为其他用户的合法access_token,就可以登录至其他用户的账户。
一般来说,如果没有其他用户的登录口令等机密信息是无法获取开放平台生成的access_token的;但攻击者可以利用自主可控的同一开放平台的第三方应用,诱导用户获取其access_token。在开放平台没有对access_token的来源及使用此token的第三方应用进行校验,或者第三方应用没有利用开放平台的接口进行对access_token的来源进行验证时,就可以使用其他第三方应用所获取的access_token替换作为攻击目标的第三方应用的access_token,从而达到登录其他用户账号的目的。
从以上流程来看,此问题的根本原因在于在使用access_token获取信息时,没有对第三方应用进行有效的认证。漏洞示例:
CSRF账户劫持 - grant、implicit
在一些第三方应用已有账号与开放平台进行账号绑定的情景下,如果在OAuth流程中没有做好对CSRF攻击的防护,在重定向的流程中,就有可能利用重定向网址进行CSRF攻击从而将受害者的第三方应用账号与攻击者的开放平台账号关联。
具体做法为首先获取代表攻击者开放平台账号的access_token,然后将此token替换到用于账号绑定的重定向链接中,诱导受害者访问。此漏洞的修复方法为利用OAuth协议的state参数做好csrf防护。
漏洞示例:
事实上,仅仅对OAuth流程中做好CSRF防护是不够的,如果第三方应用的操作本身存在CSRF漏洞,结合开放平台的登录功能的CSRF,依然有可能达成账号绑定劫持的攻击。具体可参考:
Common OAuth issue you can use to take over accounts
redirect_uri未验证
OAuth 2.0协议中开放平台会将access_token或authorization code通过redirect_uri传递给第三方应用,如果开放平台没有对redirect_uri做好验证,攻击者就可以劫持开放平台返回的敏感信息。漏洞示例:
在验证redirect_uri时,一般来说要对URL进行精确匹配,如果仅仅对同域进行验证,亦可利用改域下存在的xss漏洞劫持token。漏洞示例:
人人网Oauth 2.0授权可导致用户access_token泄露