OAuth 2.0系列(七)--- OAuth 2.0可能存在的安全漏洞及预防方法
本文将梳理一下OAuth 2.0中客户端、授权服务器和资源服务器上可能被利用的漏洞以及预防方法。
一、客户端漏洞及预防方法
对于客户端来说,最重要的信息就是客户端密钥和令牌,所以客户端上能够被利用的漏洞就是令牌和密钥失窃。
1.1 针对客户端的CSRF攻击
CSRF:cross-site request foregry,跨站请求伪造,在OAuth 2.0中的体现就是攻击者伪造一个合法的请求,并让完成身份认证的正常用户触发该请求,以达到窃取用户令牌的目的。详细流程可以用下面的图表示
如上图所示,攻击者发起攻击的前提是被攻击对象在授权服务器上完成了身份认证和授权,这样当用户无意中点击攻击者伪造的请求之后,以个人名义向授权服务器请求token(但是用户无感知),因为令牌是授权服务器针对客户端颁发的,授权服务器无法知道接收到的授权码是攻击者的还是正常用户的,因为请求来自正常用户,所以授权服务器只能给正常用户颁发令牌,但在响应结果中获取到令牌的是攻击者而非正常用户,当攻击者拿到令牌之后,就会向资源服务器请求正常用户的资源了!!!
来分析一下这个漏洞出现的原因:上图中的攻击者和被攻击者,对于授权服务器和客户端来说都是正常的用户,只要用户能完成给客户端授权的操作,他们都能通过客户端请求受保护的资源,而授权是否成功的结果是能否拿到授权码,当他们都完成了认证授权后都能拿到授权码,而且这个授权码只是用户和客户端之间的唯一标识,授权服务器不会区分授权码是哪个用户的请求,因为请求都来自客户端而非用户,所以就会出现这样一种情况:任何用户都能拿着别人的授权码向授权服务器请求令牌,而这个令牌却是自己的,反过来也是如此,攻击者正是利用了这一漏洞,使用自己的授权码伪造获取令牌的请求,并且想办法让用户在不经意间触发请求从而获取用户的令牌,想来真是妙啊!!!
既然知道了自己的软肋,那是否有办法搞个盔甲预防这种攻击呢?答案是肯定的!既然任何人都能使用自己的授权码请求别人的令牌,而且授权服务器无法区分请求令牌的授权码是不是真正的用户,那客户端就要想办法在请求令牌之前检查这个授权码是否是这个用户完成授权之后授权服务器返回的,这点对于客户端来说就是小菜一碟了,因为只有它能将用户和授权码关联起来,方法就是前面提到的state参数,这个参数看似不起眼,实则大有可用,因为它就是客户端的盔甲!OAuth核心规范中对state参数是这样描述的:
客户端用来维持请求与回调之间状态的不透明值。授权服务器将用户代理重定向回客户端时包含该值。应该使用这个参数,它可以防止CSRF跨站请求伪造。
为了预防CSRF攻击,在将用户重定向至授权服务器之前,客户端会生成一个请求上下文标识state,并将state一起发送到授权服务器上,授权服务器完成用户的认证授权后,回传state和授权码,客户端接收到回调请求后,需要校验是否有state参数,以及state是否是当前用户的上下文,如果不是则直接报错,这样一来就轻松地解决了当前用户的授权码被替换的问题,实现起来其实并不复杂,但是容易忽略!!!
1.2 客户端凭据失窃
客户端凭据失窃是针对隐式许可类型而言的,因为在有客户端的后端信道中,客户端的密钥是存储在客户端服务器的,正常来讲还是相对安全,但是在客户端和用户为一体的浏览器应用中,客户端密钥是存储在浏览器中的,这存在很大的密钥失窃风险,一旦密钥失窃,就意味着任何人都能盗取自己令牌,类似账号被盗!
解决方法:使用动态注册客户端。动态注册客户端是指不提前分配client_id和client_secret给客户端,当客户端需要使用client_id和client_secret时,先检查客户端是否存在,不存在时向授权服务器请求注册!
1.3 客户端重定向URI注册
客户端重定向URI注册,是指利用授权服务器重定向到客户端注册时提供的重定向地址进行的攻击,主要有以下两种:授权码失窃和令牌失窃。其实无论何种攻击,盗取用户的令牌进行为所欲为是攻击者的最终目的,盗取到授权码之后,可以利用CSRF攻击进一步盗取用户的令牌,可谓是步步惊心,下面来看看这两种攻击都是怎么发生的!
1.3.1 授权码失窃
授权码失窃主要是利用HTTP请求的Referer头。HTTP Referrer(据说当初被错误的拼写成Referer,沿用至今😃 )是浏览器或者一般的客户端,从一个页面跳转至另一个页面时所附加的HTTP头部字段,通过这种方式,新的Web页面就能知道请求来自哪里。但是这种攻击的前提是授权服务器在校验客户端重定向地址redirect_uri时,采用了子目录模式,而不是精确匹配,子目录模式就意味着只要请求的uri中包含注册的uri就认为是合法的请求。
举个例子🌰 :
比如客户端注册时提供的redirect_uri是https://client.com/callback,请求授权时传入的redirect_uri是https://client.com/callback/index.html,授权服务器采用子目录进行匹配时发现,请求时的值中中包含了注册时的值,则校验通过,授权成功后就会返回授权码并重定向到请求的redirect_uri上去了。画了一张图尝试解释这种攻击流程
利用Referer的请求一般有以下规则:
1.3.2 令牌失窃
令牌失窃和授权码失窃的攻击原理是相同的,都是利用客户端不规则的重定向地址和授权服务器的允许子目录校验,只不过令牌失窃是针对隐式许可类型的,而且要求客户端支持开放重定向功能(关于开放重定向的解释,参考这里:https://www.wangan.com/articles/1132)。攻击流程一般是这样的:
以上两种攻击最好的预防方式都是:
注册时客户端重定向地址redirect_uri尽可能具体,而且授权服务器校验客户端时,最好使用精确匹配,而非允许子目录。
1.3.3 小结
为了预防客户端遭到上述攻击,一个相对安全的客户端需要做到如下几点:
- 使用state参数,确保授权码和授权请求是由同一个用户发起的
- 选择合适的许可类型
- 隐式许可类型不应用用于原生应用中,它是专门供浏览器内客户端使用的
- 原生应用无法对客户端密钥进行保密,除非是动态注册的情况在在运行时配置client_secret
- 注册redirect_uri时应该尽可能地具体
- 尽量避免以URI参数形式传递访问令牌
二、资源服务器漏洞及预防方法
资源服务器的漏洞一种是前面讲的令牌失窃,另一种是受保护资源如果支持令牌通过参数传输,则攻击者通过URL参数向资源服务器请求时,很容易发起XSS攻击。
2.1 XSS攻击
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。为了和 CSS 区分,这里把攻击的第一个字母改成了 X,于是叫做 XSS。
上面的解释引自美团技术团队的文章,写得真好,详情请戳:https://tech.meituan.com/2018/09/27/fe-security.html
为什么说令牌支持参数传递的时候就容易造成XSS攻击呢,因为如果放在Header中,一般情况下在请求到达资源服务器获取资源的方法之前,一般会先经过拦截器,在拦截器中做统一处理,如果攻击者使用的是一个非法的令牌,在拦截器中校验失败,即便获取资源接口本身支持参数,也无法利用该漏洞实现XSS攻击;但是如果令牌支持参数传递,则请求会直接怼到获取资源的方法上,比如像下面这样发起请求:
https://resourceendpoint.com/getResourceInfo?accessToken=fdeggfdafgggfzd&name=<script>alert("XSS")</script>
只要用户点击的连接,就能出现弹出框,这只是一个简单的例子,真正的攻击者可不会这么温柔!
XSS的预防措施有以下几种:
- 转义所有不可信的数据,使用URI编码
- 资源服务器增加响应头Content-Type,让受保护资源返回正确的媒体类型,比如application/json,这样返回的类型就是json,会拒绝执行JavaScript中的代码
- 资源服务器增加响应头X-Content-Type-Options: nosniff,它的作用是防止在没有声明Content-Type头的情况下执行MIME嗅探
- 资源服务器增加响应头X-XSS-Protection:1;mode=block,它的作用是自动启动大多数浏览器内置的XSS过滤器来看看如何为端点添加头部(火狐不支持)
- 资源服务器增加响应头Content-Security-Policy,采用内容安全策略(content security policy,CSP),通过使用头来声明允许加载什么资源,以此降低XSS风险
- 资源服务器不允许通过查询参数传递access_token
2.2 CORS
在隐式许可类型中,客户端对资源的访问是由浏览器直接发起的,这就会出现跨域问题。跨域问题指的是当请求方与服务方的协议、主机和端口三者中,任意一个值不同时,请求将会被拒绝,因为这违背了浏览器的同源策略。该策略的目的是防止一个页面中的JavaScript代码从另外一个域加载恶意代码,但是在OAuth的这种场景中是允许这样的访问,即资源服务器不该拒绝来自不同域的客户端(浏览器)请求。
解决方法:跨域资源共享CORS,资源服务器需要增加跨域支持,Java中的实现方式一般是这样的,在springboot项目中增加CORS配置:
@Configuration
public class CorsConfig{
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); //允许任何域名
corsConfiguration.addAllowedHeader("*"); //允许任何头
corsConfiguration.addAllowedMethod("*"); //允许任何方法
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); //注册
return new CorsFilter(source);
}
}
CORS还有另外一种解决方案就是带填充的JSON(JSON With Padding,JSONP)。JSONP是开发人员用来绕过浏览器强加的跨域限制的一种技术,它让浏览器可以接受来自当前页面所在域之外其他域的数据。实际上JSON数据是通过在目标环境中加载并运行JavaScript脚本来传递的,通常会指定一个回调函数,由于数据的请求表现为一个script标签,而不是一个AJAX请求,因此能够绕过浏览器的同源策略检查。但是因为它存在安全漏洞,已经被弃用。
2.3 令牌重放
OAuth 2.0与之前版本最主要的区别之一在于其核心框架没有对加密方法做出要求,它在各种连接中都完全依赖传输层安全协议(TLS),因此OAuth生态系统中尽可能地强制使用TLS被认为是最佳时间。此外,有一个标准是专门用于此的:HTTP严格传输安全(HTTP strict transport security,HSTS),它让Web服务器能够声明浏览器在于它交互时必须使用安全的HTTPS连接,而不允许使用不安全的HTTP协议。
实现方式很简单,就是添加响应头:
Strict-Transport-Security: 'max-age=3153600'
这样以来,每次在浏览器中通过HTTP请求该端点,浏览器都会执行一个内部307重定向,这样就能防止任何意外的未加密通信。
2.4 小结
为了预防资源服务器遭到上述攻击,一个相对安全的资源服务器需要做到如下几点:
- 在受保护资源的响应中过滤素有不可信的数据
- 为每个端点选择合适的Content-Type
- 尽可能利用浏览器的防护机制以及安全头部
- 如果资源端点要使用隐式许可类型,则要使用CORS
- 避免让受保护资源支持JSONP
- 总是将HTTPS和HSTS结合使用
三、授权服务器漏洞及预防方法
3.1 常规安全
授权服务器需要和用户、客户端和资源服务器三者进行交互,它在OAuth中扮演着举足轻重的角色,所以保证其安全性是非常重要的,一般的常规安全措施有:服务器使用安全日志、有效证书的TLS、安全的宿主环境、正确的操作系统账户权限控制等。
3.2 会话劫持
问题:会话劫持是针对授权码而言的,如果授权码能够重复使用,那么攻击者就能拿着用户的授权码向授权服务器发送获取令牌请求,如果这时授权服务器不进行授权码的有效性校验,直接颁发令牌,就会让攻击者能够使用受害者的令牌了。
解决:
- 客户端不能多次使用同一个授权码。如果一个客户端使用了已经被使用过的授权码,授权服务器必须拒绝该请求,并且尽可能地吹之前通过该授权码颁发的所有令牌
- 保证授权码只会颁发给经过身份认证的客户端:如果客户端不是保密客户端,则要确保授权码只会颁发给请求中client_id对应的客户端
3.3 重定向URI被篡改
问题:这个是利用客户端注册了松散的重定向地址,并且授权服务器未使用精确匹配导致的漏洞,详情已经在1.3讲过,不再赘述
解决:
- 授权服务器要求客户端必须注册标准URI
- 开启精确匹配
3.4 客户端假冒
攻击者劫持了授权码之后,想要进一步获取令牌,需要保证下面两点:
- 保证授权码与请求上下文state一致
- 需要有客户端密钥
攻击者只要想办法实现上面两点,就能轻松获取令牌了。为了实现这一点,攻击者拦截用户授权请求,并将客户端重定向uri指向自己的页面,用户完成身份认证和授权,如果授权服务器使用宽松的redirect_uri校验,则会验证通过生成一个授权码,然后带着授权码和state信息重定向到攻击者页面,攻击者拿到授权码code和state后会伪造用户向客户端的请求(调用客户端重定向地址),因为state和code都是受害用户请求的,所以客户端验证通过并向授权服务器请求令牌,授权服务器接收到的请求会被认为是正常请求,校验通过颁发token,客户端获取到token继续请求受保护资源,资源服务器将返回请求资源,但是接收资源的人不是正常用户,而是攻击者!下面看一下这种攻击的实现流程:
上述漏洞的出现还是因为授权服务器使用了松散的redirect_uri校验,导致授权码被截取,同时在颁发令牌的验证流程中,没有校验请求授权时传入的redirect_uri和请求令牌时的redirect_uri是否一致。
预防方法:在颁发授权码之前,确保令牌请求中的重定向地址与授权请求中的重定向地址一致,这样即使授权码被盗,并且成功伪装客户端发起令牌请求,也会在授权服务器被拦截!
3.5 开放重定向器
开放重定向器,就是指在授权过程中,如果授权服务器校验参数失败,会将用户重定向到被篡改的重定向页面,攻击者可以利用该漏洞构造截取授权码或令牌(隐式许可类型),步骤如下:
- 在授权服务器https://authendpoint.com上注册一个新的客户端
- 将redirect_uri注册为https://attacker.com
- 为恶意客户端构造哪一个无效的授权请求URI。比如可以使用错误或不存在的授权范围:
https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=https://attacker.com&state=dfdgjudbetrdd&response_type=token&scope=WRONG_SCOPE
- 以正当客户端为目标,构造一个恶意的URI,将其重定向到上一步构造的URI:
https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&state=dfdgjudbetrdd&response_type=token&scope=RIGHT_SCOPE&redirect_uri=https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=https://attacker.com&state=dfdgjudbetrdd&response_type=token&scope=WRONG_SCOPE
- 如果受害用户已经使用过OAuth客户端并且授权服务器支持TOFU,那么攻击者就会收到重定向至https://attacker.com的响应,响应中可能会携带授权码或令牌。
至此,OAuth中客户端、资源服务器和授权服务器中可能存在的漏洞及预防方法梳理的差不多,下面总结一下这些内容。
3.6 小结
为了预防授权服务器遭到上述攻击,一个相对安全的授权服务器需要做到如下几点:
- 授权码使用一次之后将其销毁
- 授权服务器应该采用精确匹配的重定向URI校验算法
- 避免让授权服务器成为开放重定向器,在非法的请求中直接返回错误而不是重定向
OAuth的安全需要依靠各个组件之间的相互制约之外,各个组件在实现自己服务的过程中,不能把安全的保证机制交给其他组件去实现,而是尽可能想到其他组件可能存在的不完善,进而完善自己,所谓宽以待人严以律己!
本文来自博客园,作者:bug改了我,转载请注明原文链接:https://www.cnblogs.com/hellowhy/p/15533625.html