OAuth2.0是一套很是经典、流行的受权框架,可是咱们在学习和使用它的过程当中会以为它的受权认证流程很是繁琐,形成学习和使用OAuth2.0的时间成本很高。因此今天我将从前端安全的角度出发,使用一些针对受权流程的攻击手段和策略经过攻防演练的方式向你展现OAuth2.0是如何经过复杂的受权流程与巧妙的设计来防护攻击者的攻击。javascript
OAuth2.0是一套受权框架,它定义了 用户 经过 受权服务器 对 第三方应用 进行受权的过程。提供了让用户无需在第三方应用上输入帐号密码便可安全的对第三方应用进行受权的能力。OAuth2.0针对应用所处的运行环境制定了四种受权模式,今天咱们主要讨论其中认证流程最完整、严密的受权码模式。php
为了方便和专一于咱们今天所讨论的主题,因此我将OAuth2.0框架所定义的角色和流程缩减为下图的三个角色、四步流程。(须要格外关注的是除第一步用户受权流程是 第三方应用的客户端 与 受权服务器 进行通讯,其余流程均在 第三方应用的服务端 与 受权服务器 间通讯完成)前端
中间人攻击的主要原理是攻击者经过假装将本身置于两个端的通讯网络中,造成一个“中转站”,其主要攻击方式是对通讯信息的窃取与篡改。而针对通常受权流程(如非OAuth2.0)的中间人攻击,攻击者每每经过劫持或伪造基站、网卡、wifi等通讯服务窃取客户端与服务端中明文传输的令牌来获取用户所受权的信息与权利。java
经过简单的介绍咱们能够了解到如下几个中间人攻击的特征:数据库
在了解了中间人攻击的主要过程和特征后,咱们就能够对症下药,看看OAuth2.0是如何防护住中间人攻击的:浏览器
在整个OAuth2.0受权认证流程中,绝大部分的通讯发生在第三方应用的服务端与受权服务器之间这就能够保证这部分通讯是不会被攻击者窃取的,可是还有一部分通讯流程是可能被暴露在攻击者眼前的,就如第三方应用的客户端与服务端之间的通讯可能就被用户手机自动链接上的免费wifi所监听着。OAuth2.0对此的防护策略就是全部须要加密的、可靠的信息都由服务端之间的通讯进行交换好比令牌、密钥和用户信息等。而可能会遭受中间人攻击的客户端与服务端的通讯则默认其通讯信息已经被窃取或篡改,故而只让它交换公开的、不可靠的信息,如公钥和受权码。固然针对中间人攻击最有效的解决办法仍是经过HTTPS协议将通讯信息加密,可是OAuth2.0的出现正是解决了针对HTTP协议安全受权的问题,因此不得不讨论最坏的状况。缓存
以上咱们已经充分利用中间人攻击的第一个特征进行防护,可是毕竟终归有暴露在攻击者眼前的通讯信息:受权码。如何保证攻击者不会拿到受权码后进一步骗取受权服务器对其颁发令牌呢?这就要开始解释为何上文提到受权码是不可靠的信息,首先介绍受权码的两个特色:安全
针对受权码的两个特色咱们想象一下这样两个攻击场景,攻击者由于是中间人因此一定比客户端要提早拿到受权码,若是攻击者没有使用该受权码直接将其转给了客户端,客户端使用后因为受权码只能使用一次的特色攻击者必然没法重复使用所以攻击失败。若是攻击者率先使用了受权码,而且将使用后的受权码或伪造的受权码传递给客户端,那么客户端必然会受权失败从而引发用户的警觉。固然,光警觉不够,毕竟攻击者成功拿到了受权码,那么是什么机制拦截住了攻击者用受权码换取令牌的动做呢?就是上文咱们提到的,也是咱们下文常常要提的密钥(secret)。服务器
在介绍接下来的攻击策略前,我须要先简单介绍下应用ID(client_id)和应用密钥(client_secret)的来历和用途。在OAuth2.0中规定第三方应用在接入受权服务前要先在受权服务方注册应用,并提供应用受权回调页(图1中的第4步)等信息。注册完成后受权服务方会给第三方应用两个信息应用ID和应用密钥,应用ID是识别应用的身份码,可公开好比写死在客户端代码中,应用密钥是确认应用身份的信息,需保密只能在服务端中使用。用一句话归纳就是:应用ID告诉受权服务器“我”是谁,而应用密钥则是让受权服务器相信“我”是“我”。网络
咱们知道CSRF的主要攻击过程是构造恶意连接,诱骗用户点击。而在通常的受权过程当中攻击者每每使用一种叫作绑定劫持的攻击策略,将攻击者的帐号与被攻击者的帐号绑定在一块儿。那么如何理解绑定劫持呢?举个例子,咱们玩游戏一般最痛恨盗号狗,由于盗号者经过某些方法登陆上了咱们的游戏帐号给咱们形成了损失。而绑定劫持则偏偏相反,绑定劫持指的是攻击者诱骗被攻击者登陆了攻击者的帐号,好比被攻击者的游戏帐号被诱骗登陆并绑定了攻击者的帐号,那么攻击者即可以瓜熟蒂落的登陆上被攻击者的游戏帐号了。
在OAuth2.0受权流程中这样的恶意连接极易构建:只须要攻击者正常受权第三方应用,但将回调地址故意填错或伪造,受权服务器就会带着攻击者的受权码重定向到这个错误的地址,而这个受权码天然也就不曾被使用过。此时攻击者再将这个错误的地址改成正确的回调地址,并将这个连接发送给被攻击者,诱骗其点击。被攻击者就天然而然的进行了受权码换令牌,令牌换…的过程,这一套受权认证流程通过后就成功登陆上了被攻击者的帐号落入了攻击者的圈套。
OAuth2.0防护CSRF绑定劫持的方法很是巧妙。它经过设置一个字段state其值为hash。在第三方应用获取受权码时将其传递给受权服务器,在受权服务器带着受权码重定向到第三方应用服务端时再将state原封不动传回,第三方应用服务端会校验先后两次state是否相同。相同则证实受权者与登陆者是同一人,不相同则拒绝该登陆请求防止CSRF绑定劫持。值得思考的是state字段在OAuth2.0中是做为一个选填字段使用的,我猜测可能这种登陆上攻击者帐号的攻击方式对被攻击者的风险更小吧。
这也是一个基于CSRF的攻击策略,只不过它针对的攻击对象是相似OAuth2.0这种带有重定向操做的受权认证流程。它的攻击方式是经过构造虚假回调地址,利用受权服务对回调地址检查不严的漏洞,将被攻击者引入事先构造好的网页,窃取其受权信息。
值得注意的是,文中提到的“事先构造好的网页”不单指攻击者搭建的网站,也多是第三方应用中某些用户能够输入内容的网页,通过攻击者构造后即可以窃取受权认证信息。举个例子:假设第三方应用上有个网页支持用户评论、支持用户插入连接图片且没有将用户插入的连接地址洗成第三方应用的连接地址。那么攻击者即可以将我的服务器上的图片资源连接地址上传至该网页评论处,而后构造受权连接并将回调地址填写成该评论网页,此时可能因为该网页与第三方应用的受权页同属于相同的一级域名就绕过了检查。此时受权服务器带着受权信息重定向到这个评论网页,在查询字符串中的全部受权信息便会都在referer字段中一并暴露给攻击者。
这个问题其实不算严重,毕竟上文中间人攻击那里也说过了回调页拿到的受权码信息并不可靠,并且还有应用密钥的存在,使得受权码没法正常换取令牌。可是毕竟是重定向到了攻击者构造的页面上,如果配合其余的攻击手段可能问题会很严重,因此根治CSRF伪造重定向地址的方法就是严格校验回调地址。如下的几个回调地址就是攻击者构造的恶意地址的典型。
按照OAuth2.0最严格的回调地址校验规则来讲,应用在受权服务方注册的时候就应该已经规定好了重定向地址的路由,且不可带参数,要全等于。不过安全向来都是相对的,如果单纯的内部系统使用的受权服务,经过一下代码取到hostname后和注册应用里的hostname比较下是否全等于也就足够了:
const { hostname } = new URL(redirectURI)
复制代码
DNS污染的目的其实CSRF伪造重定向地址是相同的,都是想让被攻击者重定向到攻击者构造到网页,不一样的是DNS污染成本更高,攻击范围更广。咱们知道DNS是为浏览器提供域名到IP地址映射信息服务的分布式数据库,当下游DNS服务找不到某域名与IP地址的映射时会往上游进行请求查询。找到映射后为了下次查询的快速相应,下游DNS服务会缓存以前查询的结果,而DNS污染就是攻击者经过某些手段改写了DNS服务中的缓存。由此可想象的到,即使是受权服务器作足了回调地址的检查工做也不免会让第三方应用在查询受污染的DNS服务后跳到攻击者恶意构造的网站中。到了这一步也就只有上文提到的应用密钥能够保证攻击者拿不到令牌和用户信息。
以上展现的就是OAuth2.0防护常见的针对受权流程攻击的设计实现。在OAuth2.0设计理念中我认为最关键的核心就是 公开的信息短效且不信任、保密的信息只经过服务端传递和保存 。
复杂逻辑 + 弱约束 = 容易滋生漏洞
由于OAuth2.0流程很是复杂而且其标准对开发者的技术实现和细节约束很弱,因此在咱们开发者实现或接入OAtuh2.0 过程当中必定要明白OAuth2.0设计的原理,就好比当咱们明白了保密信息只能经过服务端传递和保存后就不会作出将令牌存放在Cookies这样的操做。
最后,OAuth2.0中还有不少细节咱们没有去讨论,好比token的生成须要回调地址的参与、做用域(scope)的安全做用。可是咱们要明白OAuth2.0对受权流程的设定绝非一个字段防护一种攻击这么简单,而是经过多个字段、流程的配合编织成一张致密的网络来完成防护功能。一样的,攻击者的攻击手段也每每不是咱们演示的这么简单、单一,每每攻击者会针对开发者功能实现上的漏洞打出一整套组合拳。因此对于咱们开发者来讲明白核心原理才能在技术细节上少出纰漏毕竟永远没有最安全,只有更安全。