从安全角度看Oauth2.0授权码模式
授权服务端必须验证client_id注册的应用与redirect_url是对应的,否则redirect_url会被伪造成第三方欺诈域名,直接导致服务器返回的code泄露。
举个例子:
https://wx.qq.com/code?
redirect_uri=https://mollytest.com&
client_id=11111&
response_type=code&
scope=userinfo
因此OAuth2.0 认证服务器对redirect_uri 的域名限制与检查是一定要做的。
<1>向授权方注册应用时,认证服务器端需要对redirect_uri进行检查禁止特殊字符输入,可以杜绝XSS攻击。
<2>认证请求发送到认证服务器时,服务器端需要对redirect_uri进行全匹配,不做模糊匹配。
应用注册成功后,授权方服务将以client_id和client_secret的形式为应用发布客户端凭证,标识应用程序。
●Client_id是公开透明的字符串,存储在第三方应用客户端,用于构建呈现给用户的授权 url 。
●Client_secret用于验证应用身份,存储在第三方应用服务端,在客户端和服务之间保持私有性。
如果 client_id 是为了告诉身份认证服务器,『我是 molly.com』,那么 client_secret 则是为了告诉身份认证服务器,『我真的是 molly.com!』。
OAuth 2.0 当初设计的一个目标之一是,让不支持 HTTPS 的网站也能安全使用。既然提到了 HTTPS,必然跟中间人攻击有关系。继续往下说之前,我们再确认一下 OAuth 认证的要满足的条件:
<1>要获取 access_token,必须先让用户在身份认证服务器上完成账户密码的输入(或已在登录时确认授权),因为不能把账号密码暴露给第三方应用。
<2>access_token 必然要悄悄给第三方应用,因为不能被攻击者看到。
我们先假设 OAuth 不需要整什么 code,就直接获取 access_token:
<1>用户浏览器访问 molly.com,molly.com 服务器发现用户处于未登录状态,返回 302,让浏览器跳转到授权服务器 OAuth 服务获取 access_token,https://wx.qq.com/token?
client_id=xxx&
redirect_uri=http://molly.com&
scope=...
<3>用户浏览器访问带 access_token 的链接,完成整个登录。
此流程没大毛病,就是最后一步,如果 molly.com 不是 HTTPS 的网站,那么 access token 就等于暴露在浏览器和 chrisyue.com 服务器之间的线路中了。
可能也有人会问,那多一步获取 code 有什么用呢?
如果molly.com 不支持 HTTPS,code 不也是会被暴露吗?攻击者通过嗅探的方式截获到请求中传输的code,基于此进一步获取 access _token来登录受害人的账号。
针对这个问题,OAuth2.0 协议其实对此是有处理方式的:
<1>首先,授权服务端生成的临时code必须是一次有效,第三方应用使用一次后立即失效并且有效期很短,一般推荐30s。
也就是说,攻击者最多让正常用户有点困扰,可能会出现登录意外失败,或者明明看起来登录成功但还是获取不到用户信息的情况(access_token 已经失效),但攻击者依旧拿不到数据。
假设code 直接被发送到攻击者的服务器上,code 可是不会像上面说的那样会被执行两次,假如获取 access_token 不需要 access_secret,攻击者直接就拿 code 换 access_token 了。
说到此 access_secret 的意义也不用多说了。因为access_secret 就是用来告诉 OAuth 服务器,『我真的是 xxx 网站不是假装的』,它其实就是第三方网站与 OAuth 服务网站之间的信物。此信物是一定一定不能被第三者知道的。如果知道了一定要第一时间重新生成。
state 参数是为了保证申请 code 的设备和使用 code 的设备的一致而存在的。
与 CSRF 攻击类似,如果 state 参数为空,作为攻击者,
<1>先打开有道云官网,专门用于攻击他人的账号;
<2>然后走正常流程,使用QQ账号授权登录;
<3>授权成功之后,qq返回 code 回跳到 youdao.com,这个时候,攻击者拦截自己的请求让他不再往下进行,而直接将带 code 的链接发给受害者,并欺骗受害者点击;
<4>受害人点击链接之后,继续攻击者账号的登录流程,不知不觉登录了攻击者的账号;
<5>受害者如果这个时候没察觉此账号不是他本人的,在有道云笔记上传了一些隐私数据等,攻击者后面就能通过自己的账号看到。
而 state 参数如果利用起来,当作 CSRF Token,就能避免此事的发生:
<1>攻击者依旧获取 code 并打算骗受害者点击。
<2>受害者点击链接,但因为有道云服务器分配给受害者的 state 值和链接里面的(分配给攻击者的)state 值不一样,有道云服务器直接返回验证 state 失败。
state 或者说 CSRF Token 这种跟设备绑定的随机字符串,只要稍微复杂一点,攻击者根本就不可能猜得出来,而设置一个让攻击者猜不到的,跟设备或者说浏览器绑定的 state (CSRF token 同理) 值,就是解决 CSRF 攻击的关键。
由于Access_Token是通过http协议从服务器端传输给客户端,为了防止旁路监听泄露Access_Token,服务器必须提供https来保证传输通道的安全性(TSL的要求);
第三方应用获取Access_Token,应该在第三方应用服务端与授权服务端交互获取Access_Token,原则是不允许Access_Token传给前端直接使用(但是现在很多系统使用不规范,会把Access_token作为自己应用的会话标识传到前端);
需要保证Access_Token信息的不可猜测行,以防止被猜测得到。
Refresh_token存储在第三方应用服务端,维持refresh_token和第三方应用的绑定,刷新失效机制的设计不允许长期有效的access_token存在。