OAuth 2.0 有 4 种认证流程:
授权码模式(authorization code)
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)
下面以微信为例介绍最常见的也是最安全的 Authorization Code认证流程。
微信OAuth2.0授权登录让微信用户使用微信身份安全登录第三方应用或网站,在微信用户授权登录已接入微信OAuth2.0的第三方应用后,第三方可以获取到用户的接口调用凭证(access_token),
通过access_token可以进行微信开放平台授权关系接口调用,从而可实现获取微信用户基本开放信息和帮助用户实现基础开放功能等。
微信OAuth2.0授权登录目前支持authorization_code模式,适用于拥有server端的应用授权。该模式整体流程为:
获取access_token时序图:
下面具体介绍一下微信对这个协议的具体实现过程。
第1步:开发者在微信开放平台申请接入并成功获取到appid和AppSecret,并配置回调域名。
第2步:构造微信登录二维码的超链接如下:
参数说明
参数
是否必须
说明
appid
是
应用唯一标识(前面认证网页应用中获得)
redirect_uri
是
重定向地址,需要进行UrlEncode(前面认证网页应用中获得)
response_type
是
填code
scope
是
应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi\_login即可
state
否
用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
返回说明
用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数
redirect_uri?code=CODE&state=STATE
若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
redirect_uri?state=STATE
实际抓包示例:
其中appid参数为开发者在第一步中申请到的appid, scope参数为授权应用的权限列表,redirect_uri为授权成功后的回调地址。
第3步:假如用户同意授权,在微信登录成功后会跳转到redirect_uri参数指定的URL,并在URL尾部追加code参数(即Authorization Code),如上述示例则会跳转到:
然后,我们可以通过Authorization Code去获取用户openid和access\_token,进而获得用户的信息。
第4步:通过Authorization Code获取Access Token和openid,服务器端构造如下请求即可获取Access Token和openid:
参数解释如下:
grant_type
授权类型,此值固定为“authorization_code”。
client_id
申请微信登录成功后,分配给网站的appid。
client_secret
申请微信登录成功后,分配给网站的appkey。
code
上一步返回的Authorization Code。
redirect_uri
与上面一步中传入的redirect_uri保持一致。
返回说明
正确的返回:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数说明:
参数
说明
access_token
接口调用凭证
expires_in
access_token接口调用凭证超时时间,单位(秒)
refresh_token
用户刷新access_token
openid
授权用户唯一标识
scope
用户授权的作用域,使用逗号(,)分隔
unionid
当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。
错误返回样例:
{"errcode":40029,"errmsg":"invalid code"}
第5步:使用Access Token以及OpenID来访问用户数据
构造如下请求即可访问用户数据:
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
参数说明
参数
是否必须
说明
access_token
是
调用凭证(上一个请求中获得)
openid
是
普通用户的标识,对当前开发者帐号唯一(上一个请求中获得)
lang
否
国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为zh-CN
返回说明
正确的Json返回结果:
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数
说明
openid
普通用户的标识,对当前开发者帐号唯一
nickname
普通用户昵称
sex
普通用户性别,1为男性,2为女性
province
普通用户个人资料填写的省份
city
普通用户个人资料填写的城市
country
国家,如中国为CN
headimgurl
用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
privilege
用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
unionid
用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
错误的Json返回示例:
{
"errcode":40003,"errmsg":"invalid openid"
}
在上述实现的第二步中将redirect_uri修改为攻击者控制站点,用户在授权登录后将携带Authorization Code跳转到攻击者控制站点,攻击者从URL参数中即可获得Authorization Code并实现用户劫持。服务端必须验证client_id(APPID)和redirect_uri规定的域一致,如果不一致则无法登陆。
其实腾讯在实现第三方登录接入的时候早就考虑过这种老套的攻击方式,于是,开发者在集成微信登录时必须在微信开放平台上填写网站的回调地址,在进行登录验证的时候如果redirect_uri中的值与设置好的回调地址不同则会拒绝访问:
这样就防止了攻击者篡改redirect_uri为恶意站点的钓鱼攻击。
但是现在又提出了一种看似合理的绕过方法:
利用合法网站的URL重定向漏洞绕过redirect_uri中的域名白名单限制。
假设我有一个合法的网站whitehat.com,攻击者控制一个恶意站点hacker.com
攻击者可以构造这样一个链接来绕过redirect_uri中的域名白名单限制:
http://whitehat.com/index.php?Redirect=http%3a%2f%2fhacker.com%2findex.php
其中Redirect参数指定的为重定向地址
这样的话,把这个URL地址传给redirect_uri即可构造一个恶意链接,实现用户授权微信登录后跳转到hacker.com
但是用户的授权令牌Authorization Code真的会被传送到hacker.com吗?
我们把上述URL传给redirect_uri,跳转到的URL地址如下:
http://whitehat.com/index.php?Redirect=http%3a%2f%2fhacker.com%2findex.php?code=****
细心的人已经发现了,这个链接还是跳转到http://hacker.com/index.php而不是http://hacker.com/index.php?code=****
这是因为code参数前面的&符号没有URL编码,因此code参数被whitehat.com处理而不是属于Redirect参数的一部分。
因此,第一种攻击模型只能用来构造登录后的钓鱼攻击,通常情况下Authorization Code不会被传送到攻击者控制的站点中。
将第二步实现的redirect_uri改为可以引入外链的合法URL地址,这样当合法用户登录后加载此页面的外链时,攻击者就可以从其控制的服务器中在referer消息头中获得泄露的Authorization Code。据说这种攻击方法横扫国内各大站点,这个攻击方法在此RFC文档的Security Considerations中已经提到过:
[
![](https://p4.ssl.qhimg.com/t01cfbabdef04caa26d.png)
](https://p4.ssl.qhimg.com/t01cfbabdef04caa26d.png)
同时也给出了相应的安全建议:
[
![](https://p1.ssl.qhimg.com/t01cad5a18e9cf3e24b.png)
](https://p1.ssl.qhimg.com/t01cad5a18e9cf3e24b.png)
即Authorization Code在获取后必须在短时间内失效而且只能被使用一次。这种方法在理论上确实可以有效的阻止上述的攻击方式,情景分析如下:
(1)攻击者构造恶意链接发送给用户,其中redirect_uri=http://bbs.test.com/index.php
(2)用户点击链接登录后,回调地址为:http://bbs.test.com/index.php?code=****
(3)用户携带code向服务器发送请求加载此页面,加载的页面中含有攻击者放置的外链(例如头像中的图片链接等),用户加载外链中的图片,攻击者从referer消息头中获得用户的code
由于Authorization Code是通过redirect_uri浏览器回调传输,容易被截取,服务器生成的临时Authorization Code必须是一次有效,客户端使用一次后立即失效并且有效期很短,一般推荐30s有效期,可以保证临时Authorization Code被客户端正常消费后不会被再次使用。
比如构造一个认证请求,redirect_uri = http://app.com/test?callback=
服务器端需要对redirect_uri进行检查禁止特殊字符输入,并且对redirect_uri进行全匹配,不做模糊匹配可以杜绝XSS攻击。
第2步认证请求url中state参数是最容易被忽略的,大部分IDP不会强制要求客户端使用state参数。与 CSRF 攻击类似,如果 state 参数为空,作为攻击者,
1\. 先申请一个新的,专门用于攻击他人的账号;
2. 然后走正常流程,跳到微信上去登录此账号;
3. 登录成功之后,微信带着 code 回跳到第三方站点,如www.test.com,这个时候,攻击者拦截自己的请求让他不再往下进行,而直接将带 code 的链接发给受害者,并欺骗受害者点击;
4. 受害人点击链接之后,继续攻击者账号的登录流程,不知不觉登录了攻击者的账号
受害者如果这个时候没察觉此账号不是他本人的,传了一些隐私文件,如照片啥的,攻击者立马就能通过自己的账号看到。
而 state 参数如果利用起来,当作 CSRF Token,就能避免此事的发生:
1\. 攻击者依旧获取 code 并打算骗受害者点击
2. 受害者点击链接,但因服务器(比如 www.test.com)分配给受害者的设备的 state 值和链接里面的(分配给攻击者的)state 值不一样,服务器(test.com)直接返回验证 state 失败。
所以安全的实现是:
客户端每次请求生成唯一字符串在请求中放到state参数中,服务端认证成功返回Authorization Code会带上state参数,客户端验证state是否是自己生成的唯一串,可以确定这次请求是有客户端真实发出的,不是黑客伪造的请求
OAuth2.0协议安全性进行进一步增强。
参考资料:
[https://cloud.tencent.com/developer/article/1447723](https://cloud.tencent.com/developer/article/1447723)
[https://www.anquanke.com/post/id/98392](https://www.anquanke.com/post/id/98392)
[https://www.freebuf.com/articles/web/252254.html](https://www.freebuf.com/articles/web/252254.html)
[https://xz.aliyun.com/t/2260](https://xz.aliyun.com/t/2260)
[https://wooyun.js.org/drops/OAuth%202.0%E5%AE%89%E5%85%A8%E6%A1%88%E4%BE%8B%E5%9B%9E%E9%A1%BE.html](https://wooyun.js.org/drops/OAuth%202.0%E5%AE%89%E5%85%A8%E6%A1%88%E4%BE%8B%E5%9B%9E%E9%A1%BE.html)