OAuth 2.0 机制原理

OAuth 2.0 机制原理OAuth 2.0是目前最流行的授权机制,用来授权第三方应用,获取用户数据

尽管OAuth 2.0是当前标准,但一些网站仍在使用旧版本1a。OAuth 2.0是从头开始编写的,而不是直接从OAuth 1.0开发的。结果,两者非常不同。目前,术语 “OAuth” 专指OAuth 2.0

OAuth就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用

( 如图便是一种 OAuth 授权登录 )

简单来说:第三方登录,实质就是OAuth授权。

用户想要登录A网站,A网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要OAuth授权

OAuth身份验证的结果大致类似于基于SAML的单点登录 (SSO)

比如说用户登录网站A需要进行身份验证,且允许使用Google账户登录:

A网站让用户跳转到GoogleGoogle要求用户登录,用户登录Google后,Google询问用户:A网站要求获取xxx权限/xxx信息,是否同意?用户同意,Google就会重定向回A网站,并发回一个授权码A网站使用授权码向Google请求令牌Google返回令牌给AA网站使用令牌,向Google请求用户身份如果说的官方一点,就是:

OAuth在 "第三方应用" 与 "服务提供商" 之间,设置了一个授权层

"第三方应用" 不能直接登录 "服务提供商" ,只能登录授权层,以此将用户与客户端区分开来

"第三方应用"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。"第三方应用"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"第三方应用"开放用户储存的资料 当用户登录了第三方应用后,会跳转到服务提供商获取一次性用户授权凭据),再跳转回来交给第三方应用,第三方应用的服务器会把授权凭据和服务提供商给它的身份凭据一起交给服务方,这样服务方既可以确定第三方应用得到了用户服务授权,又可以确定第三方应用的身份是可以信任的,最终第三方应用可以顺利的获取到服务商提供的web API的接口数据

应用场景现在各大开放平台,如微信开放平台、腾讯开放平台、百度开放平台等大部分的开放平台都是使用的OAuth 2.0协议作为支撑

客户端App使用三方登录;微信小程序登录授权;多个服务的统一登录认证中心、内部系统之间受保护资源请求令牌(token)令牌在OAuth中验证身份的作用上与使用密码是一致的,但其有以下特点:

令牌生命周期较短,到期遍会失效令牌可被数据所有者撤销,且之后基本不会产生相同的令牌令牌对权限的限制更为严格更加细化,比如说:读令牌、写令牌等以上的有点让OAuth在身份验证的过程中使用了令牌而不是密码,以上也可以说是OAuth的优点

但是虽然具备以上优点,令牌依旧必须保密,令牌泄露的后果和密码泄露一样严重

获得令牌的方式综上所述:OAuth的核心就是通过设置一个授权层,向第三方应用颁发令牌

OAuth 2.0规定了四种获得令牌的方式:

授权码(authorization-code)隐藏式(implicit)— 危险密码式(password)客户端凭证(client credentials)其中有两个概念容易弄混:

授权码:请求令牌令牌:到用户进行了认证的地方请求用户的数据授权码authorization-code授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌

也就是最开始举例子所采用的方式,该方式是最常用的流程,也是最安全的

该方式适用于有后端的WEB应用,这样授权码通过前端传送,令牌则存储在后端,所有与资源服务器的通信都在后端完成,前后端分离,避免令牌被泄露

接下来用Burp靶场中的链接(做了一些改动)为例进行举例(后面会完整打该靶场)

第一步:在登陆时,要登陆的网站A会提供一个链接,点击后便会被重定向到B网站,进行身份认证并决定是否要将数据给A网站使用。

下面是A网站跳转B网站的示意链接

https://oauth-xxxxxx.web-security-academy.net/auth?

client_id=xzx6wagk39j6dni18kdl0&

redirect_uri=https://xxxxxx.web-security-academy.net/oauthcallback&

response_type=code&

nonce=879645905&

scope=openid profile email

其中参数的含义为:

response_type表示要求返回的类型

code表示要求返回授权码

client_id让B直到是谁向其发送的请求,用于标识请求的发送方

xzx6wagk39j6dni18kdl0

redirect_uri标识B网站接受或拒绝请求后跳转的网站

https://xxxxxx.web-security-academy.net/oauth-callback

scope表示要求的授权范围

openid profile emailGoogle 平台默认的scope就是这三个

USER_OPENID("openid", "Associate you with your personal info on Google", true),

USER_EMAIL("email", "View your email address", true),

USER_PROFILE("profile", "View your basic profile info", true),

第二步:用户跳转后,B网站会要求用户登录,用户登陆后,B会询问是否同意给予A网站授权,用户同意后,B网站就会跳到redirect_uri参数指定的网址。跳转时,会传回一个授权码

https://xxxxxx.web-security-academy.net/oauth-callback?code=AUTHORIZATION_CODE

第三步:A网站拿到B返回的授权码后,便可以在后端通过授权码向B网站请求令牌了

[https://oauth-authorization-server.com/token?

client_id=12345&

client_secret=SECRET&

redirect_uri=https://clientapp.com/callback&

grant_type=authorization_code&

code=a1b2c3d4e5f6g7h8&

scope=openid email profile]

client_id与client_secret用于让B确认A的身份,而且client_secret是保密的,所以为保证安全,该请求一般只在后端发送

grant_type=authorization_code

表示使用的授权方式为授权码

code=a1b2c3d4e5f6g7h8

上一步拿到的授权码

https://client-app.com/callback

令牌颁发后的回调网址

第四步:B网站收到请求以后,就会颁发令牌

具体做法是向redirect_uri指定的网址,发送一段JSON数据

{

"access_token": "z0y9x8w7v6u5",

"token_type": "Bearer",

"expires_in": 3600,

"scope": "openid email profile",

}

其中access_token字段就是令牌

隐藏式implicit不安全

有些Web应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端(当然不排除有错误配置为该模式的情况)

RFC 6749就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)

第一步:A 网站提供一个链接,要求用户跳转到B网站进行登录,并将B网站的用户数据授权给A网站使用

/authorization?

client_id=12345&

redirect_uri=https://clientapp.com/callback&

response_type=token&

scope=openid profile&

state=ae13d489bd00e3c24

其中重点关注的参数为:

response_type=token代表要求直接返回令牌第二步:用户跳转到B网站进行身份的认证,登录后若用户同意授予A网站访问用户数据的权力,则B网站就会跳转到redirect_uri所指定的网址,并且把生成的令牌作为URL参数,传递给A网站

https://clientapp.com/callback#token=ACCESS_TOKEN其中ACCESS_TOKEN部分就是返回的令牌

此处返回令牌时并没有以查询字符串的形式返回,而是以锚点(fragment)的形式返回,主要受历史原因影响,由于OAuth2.0允许跳转的网站是HTTP协议,存在攻击的可能,而浏览器跳转时,锚点不会被发到服务器,减少令牌在传输过程中泄露风险

[但注意,这种将令牌直接传给前端的方式依旧是非常危险的,避免使用该方式若必须使用,要保证对令牌有效期控制的很短)]

锚点#:

#代表了网页中的一个位置,其右面的字符串就是该位置的标识符,比如在支持锚点的网页中,例如输入以下URL:

https://www.shtwo.top/pwn1#canary访问时就会将canary位置滚动到可视区域

锚点的特性

HTTP请求不包括#:锚点的作用在于告诉浏览器定位到哪里,与服务端没有关系,所以发出HTTP请求时不会携带锚点以及后面的字符串

也就是说https://www.shtwo.top/?color=#fff实际上发出HTTP请求时报文中请求的URI实际是?color=

这也就解释了为什么上述令牌要放在#后面返回

这种过滤方式是在浏览器层面进行的过滤,所以若将#URL 编码为%23就可以成功发送:

https://www.shtwo.top/?color=%23fff

默认情况下,搜索引擎会忽略URL中#的部分

由于搜索引擎会忽略#后面的部分,所以若是希望Ajax生成的内容被浏览引擎读取,会有麻烦,此时可以使用#!来代替,Google会自动将#!转成查询字符串__escaped_fragment__的值

密码式password非常不安全

RFC 6749也允许用户直接提交用户名和密码,待登录网站直接拿着用户名和密码到认证网站认证身份,申请令牌

第一步:A网站直接要求用户提供B网站的用户名和密码,拿到以后,A就直接向B请求令牌

/token?

client_id=12345&

grant_type=password&

username=^USERNAME^&

password=^PASSWORD^&

第二步:B网站验证身份通过后,直接给出令牌

此时不需要跳转,直接把令牌以JSON的形式通过HTTP回应

毫无疑问,非常不安全

凭证式client credentials该方式一般不用来请求令牌,多用于多用户共享同一令牌时进行令牌的换

第一步:A应用在命令行向B发出请求

/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

第二步:B验证通过以后,直接返回令牌

使用令牌A网站拿到令牌以后,就可以向B网站的API请求数据了。

此时,每个发到B的API的请求用户信息的请求,都必须通过在HTTP头部添加Authorization字段带上令牌

具体格式为:

Authorization: Bearer ACCESS_TOKEN

更新令牌B网站颁发令牌的时候,一般会一次性颁发两个令牌:

获取数据access_token刷新获取新的令牌refresh_token在申请的access_token令牌到期前,用户使用refresh token发出刷新令牌请求,即可更新令牌

/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN

OAuth 2.0 存在的问题了解了 OAuth 2.0的原理后,可以发现其可能存在的问题/漏洞,大多数源于其缺乏内置的安全机制,与开发者/使用者是否采取了正确的获取令牌的方式息息相关,那么接下来就通过Burp官方提供的靶场来说几种存在安全风险的情况

至于如何判断当前是一个OAuth2.0的认证,方法很多,而且其特征很明显,相信大家通过前面的介绍已经可以区分,此处就不过多介绍了

风险点 1 :使用不合适的方式获取令牌存在风险的令牌获取方式:

隐藏式implicit密码式password且不对传输的access_token或username/password加密的话,就会造成中间人攻击

靶场信息:

目标:

客户端应用程序的错误验证使攻击者有可能在不知道密码的情况下登录其他用户的帐户。

要解决实验室问题,请登录Carlos的帐户。他的电子邮件地址是carlos@carlos-montoya.net

靶机地址:Lab: Authentication bypass via OAuth implicit flow | Web Security Academy

基本信息:进入靶场后,到登陆界面登陆时发现进行的是基于 OAuth 2.0的身份认证方式

于是打开Burp进行抓包

通过其请求包中的参数response_type=token发现采用的获取令牌的方式为:隐藏式

采用该方式说明access_token令牌会被传回也就意味着可以被获取到,但前提是返回的带有令牌的JSON信息不被加密

果然其返回的令牌token为:

于是可以看到当前网站向提供认证的网站发出了请求的报文中带上了这个token

有了这串token,我们就可以找到刚刚使用该token的身份认证的报文中,修改其email达到绕过的目的

将该报文发到Repeater进行重放,并替换其email字段

此时得到了一个用于Set-Cookie的新session并进行302的重定向,将其生成对应的URL

复制到浏览器请求

大功告成

风险点 2 :缺乏随机 token 值此处的token值得是用于预防CSRF所使用的token,并不是前面所说的access_token,由于当当前网站成功获得了用来访问存在着认证完用户信息的网站时(获取了access_token),需要使用access_token这个令牌确认当前的身份,那么这个access_token的作用就很像CSRF中经常尝试获取并利用的session cookie一样,可以代表当前用户的身份,所以若不加以限制,势必会造成 CSRF 攻击

而对于OAuth中,常用state参数来传递一个随机的用户防止CSRF的token值

/authorization?

client_id=12345&

redirect_uri=https://clientapp.com/callback&

response_type=token&

scope=openid profile&

state=ae13d489bd00e3c24

拿最开始的例子来说,可以明显看到其携带了state参数,且是一个随机数

所以若是进行了错误的配置,没有启用state参数,就有可能造成CSRF攻击

靶机见:

Lab: Forced OAuth profile linking | Web Security Academy

风险点 3:不完善的redirect_uri限制通过前面的学习,我们知道在身份服务器验证用户身份(成功或失败)后,需要302跳转到redirect_uri处,那么对于重定位的地址,若不进行限制就可以跳转到任意构造好的URL,通过恶意构造iframe标签,引导用户点击发起访问,这也可导致最终的令牌被发送到攻击者构造的 URL 处被攻击者获取

所以,客户端应用程序的最佳做法是在向OAuth服务注册时提供其真实回调URI的白名单

靶机见:

Lab: OAuth account hijacking via redirect_uri | Web Security Academy

参考文章/深入学习自定义Scope

OAuth 2.0 authentication vulnerabilities | Web Security Academy

OAuth2.0原理浅析

00 开篇词 为什么要学OAuth 2.0?.md

OAuth2.0认证和授权机制讲解