OAuth2.0安全设计之Authorization Code

(给ImportNew加星标,提高Java技能)

转自:bamb00

链接:www.cnblogs.com/goodhacker/p/14457014.html

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步:构造微信登录二维码的超链接如下:
  • https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

    参数说明
    参数

    是否

    必须

    说明
    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
    实际抓包示例:
  • https://open.weixin.qq.com/connect/qrconnect?response_type=code&appid=wx2198c66352420194&redirect_uri=https%3A%2F%2Fpassform.test.com%2Fv3%2Fweb%2Flogin%2FwechatCallBack%3FisPc%3D1%26randomNum%3D%26redirect_uri%3Dhttps%253A%252F%252Fwww.test.com%252F%253Fopenid%253D33336839a7398ce8%26client_id%3D30&scope=snsapi_login&state=1614336736067
    其中appid参数为开发者在第一步中申请到的appid, scope参数为授权应用的权限列表,redirect_uri为授权成功后的回调地址。
    第3步:假如用户同意授权,在微信登录成功后会跳转到redirect_uri参数指定的URL,并在URL尾部追加code参数(即Authorization Code),如上述示例则会跳转到:
  • https://passform.test.com/v3/web/login/wechatCallBack?isPc=1&randomNum=&redirect_uri=https%3A%2F%2Fwww.test.com%2F%3Fopenid%3D33336839a7398ce8&client_id=30&code=053isZFa12PMAA0NVeGa1yR9300isZFe&state=1614336736067
    然后,我们可以通过Authorization Code去获取用户openid和access_token,进而获得用户的信息。
    第4步:通过Authorization Code获取Access Token和openid,服务器端构造如下请求即可获取Access Token和openid:
  • https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
    参数解释如下:
     
     
    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"}
    三、常见不安全设计造成的风险
    风险1:redirect_uri回调域名欺骗 (1)未验证redirect_uri是否与注册的回调地址匹配
    在上述实现的第二步中将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不会被传送到攻击者控制的站点中。
    (2)未设置Authorization Code使用一次就失效
    将第二步实现的redirect_uri改为可以引入外链的合法URL地址,这样当合法用户登录后加载此页面的外链时,攻击者就可以从其控制的服务器中在referer消息头中获得泄露的Authorization Code。据说这种攻击方法横扫国内各大站点,这个攻击方法在此RFC文档的Security Considerations中已经提到过:
     
     
    同时也给出了相应的安全建议:
     
     
    即Authorization Code在获取后必须在短时间内失效而且只能被使用一次。这种方法在理论上确实可以有效的阻止上述的攻击方式,情景分析如下:
    (1)攻击者构造恶意链接发送给用户,其中redirect_uri=http://bbs.test.com/index.php
    ( 2)用户点击链接登录后,回调地址为:http://bbs.test.com/index.php?code=****
    (3)用户携带code向服务器发送请求加载此页面,加载的页面中含有攻击者放置的外链(例如头像中的图片链接等),用户加载外链中的图片,攻击者从referer消息头中获得用户的code
    风险2:redirect_url XSS跨域攻击
    由于Authorization Code是通过redirect_uri浏览器回调传输,容易被截取,服务器生成的临时Authorization Code必须是一次有效,客户端使用一次后立即失效并且有效期很短,一般推荐30s有效期,可以保证临时Authorization Code被客户端正常消费后不会被再次使用。
    比如构造一个认证请求,redirect_uri = http://app.com/test?callback=<script src="http://app2.com?getToken.php"></script>
    服务器端需要对redirect_uri进行检查禁止特殊字符输入,并且对redirect_uri进行全匹配,不做模糊匹配可以杜绝XSS攻击。
    风险3:未添加State 防止CSRF
    第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是否是自己生成的唯一串,可以确定这次请求是有客户端真实发出的,不是黑客伪造的请求
    风险4:Access_Token泄露
  • 由于Access_Token是通过http协议从服务器端传输给客户端,为了防止旁路监听泄露Access_Token,服务器必须提供https来保证传输通道的安全性(TSL的要求)
  • 客户端获取Access_Token,应该在后台与服务端交互获取Access_Token,不允许Access_Token传给前端直接使用
  • 需要保证Access_Token信息的不可猜测性,以防止被猜测得到
  • 风险5:令牌有效性漏洞
  • 维持refresh_token和第三方应用的绑定,刷新失效机制的设计不允许长期有效的token存在;
  • 四、增强OAuth2.0协议设计及使用规范
    OAuth2.0协议安全性进行进一步增强。
  • 对颁发出去的token权限进行限制,不同用户申请的token根据人员所属组织、角色、岗位进行数据隔离
  • 对登录过程安全性增强,对登录验证方式进行丰富,支持静态密码、手机验证码、OTP、生物识别、FIDO
  • 对Token颁发后的生命周期管理,可以按策略主动注销颁发的Token
  • 对使用OAuth过程进行行为分析,对登录过程进行风险识别
  • 按照不同应用的安全等级进行分级,不同安全级别应用实现二次认证,保障关键系统的安全访问
  • 参考资料:
     
     
    https://cloud.tencent.com/developer/article/1447723
    https://www.anquanke.com/post/id/98392
    https://www.freebuf.com/articles/web/252254.html
    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


    推荐阅读   点击标题可跳转

    OAuth2和JWT - 如何设计安全的API?

    OAuth2 实现单点登录 SSO

    OAuth 2.0 认证的原理与实践



    看完本文有收获?请转发分享给更多人

    关注「ImportNew」,提升Java技能

    好文章,我在看❤️