从甲方安全视角看漏洞(一)——跨站请求伪造

  • A+
所属分类:安全文章

说起安全漏洞,如果你是一名甲方安全人员,那么除了漏洞的原理和危害之外,如何防御和修复漏洞也是需要重点关注的问题。从甲方安全视角看漏洞这个系列就和大家一起聊一聊甲方安全工作中处理安全漏洞关注的各类问题。


提到跨站请求伪造漏洞(CSRF),相信有很多在甲方公司工作的小伙伴和笔者一样,会觉得这个漏洞伤害不高,但侮辱性极强。原因是这个漏洞十分贴近业务,大多数时候风险有限,但修复起来却要消耗大量的时间和精力。一方面如果想全站修复的话,有的代码是祖传的,无法增加全局拦截器,逐个接口修复又过于劳民伤财。另一方面,“一千个开发人员心中有一千个CSRF”,大家对这个漏洞都有着自己的见解,有时即便反复沟通修复方案,最终的修复结果仍然差强人意。所以本文就老洞新谈,重点聊一聊CSRF漏洞在企业中的修复和防御问题。



一、老生常谈,温习下CSRF漏洞原理

CSRF(Cross-SiteRequest Forgery)跨站请求伪造,是一种对网站请求的恶意利用,虽然跟跨站脚本漏洞(XSS)很像,但原理却并不相同。XSS可以获取用户凭证,而CSRF只是利用用户凭证在用户不知情的情况下执行某些重要操作。通俗的说XSS可以执行任意JS,而CSRF只是请求伪造而已。

CSRF漏洞的原理说白了,就是利用浏览器的同源策略,只要在同一浏览器内发送与目标网站同源的请求,浏览器都会默认带上该网站的Cookie信息。那么只要受害者没有在目标网站退出登录,攻击者就可以通过发送构造好的CSRF脚本或者包含CSRF脚本的链接,盗用受害者的身份,执行一些用户不想做的操作。比如以受害者的名义发送邮件、发消息、添加系统管理员,甚至于购买商品、虚拟货币转账等。攻击过程大致如下:

从甲方安全视角看漏洞(一)——跨站请求伪造

1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。


二、针对CSRF漏洞的防御方案

了解攻击原理后,接下来我们详细讨论下各种防御方案。


1) 验证码

从安全性来讲,最强大的无疑是验证码方案,因为一个完善的验证码方案可以证明当前操作来源于正确的用户,这也就从根本上起到了防御CSRF的作用。所以不考虑用户体验的问题,为重要或敏感操作增加验证码也是一种可行的防御手段。


2) Token防护anti CSRF Token

这种防御方案应该是目前使用最多最稳定的方案,具体的做法是:

服务端随机生成Token,保存在服务端Session中,同时保存到客户端中,客户端发送请求时,把Token带到HTTP请求头或参数中,服务端接收到请求,验证请求中的Token与Session中的是否一致。


从甲方安全视角看漏洞(一)——跨站请求伪造

这个方案适用于前后端不分离的项目和前后端分离的项目。对于前后端不分离的项目,Token可以直接在编译模板的过程中写到表单的隐藏字段中,这样发送请求不需要额外的操作;而对于前后端分离的项目,Token可以在登录时写入到Cookie中,发送请求时,JS读取Cookie中的Token,并设置到HTTP请求头或请求体中。


需要特别注意的是,有时候开发人员会认为将Token缓存在Cookie中,后续再读取Cookie中的Token做对比即可防御攻击。但正确的做法是在客户端发送请求的时候必须将Token从Cookie中取出放在请求头或请求体中(放在url中可能会被攻击者通过Referer获取)。因为Token防御的主要原理是同源策略的限制,攻击者的恶意链接由于涉及跨域并不能读取合法用户Cookie中的具体内容,自然无法读取到正确的Token。此外,如果相关请求没有放御重放攻击的需求,Token不需要在每次请求后更换。


Token防护是目前企业中最主流的防御方式,但是正如前言中所说,对于老旧的系统等无法增加全局拦截器的情况,这种方式可能会给修复人员带来很大的压力,再结合漏洞的危害等级,往往使修复工作陷入僵局。


3) 验证 HTTP Referer 字段

根据HTTP协议,HTTP头包含字段Referer,该字段的内容是请求的来源地址。那么我们就可以在服务器在收到请求后,验证http头的字段referer来源地址,是不是合法地址,如果是就响应请求,否则就拒绝响应。

从甲方安全视角看漏洞(一)——跨站请求伪造


这种防御方式的正确使用主要是要注意两点:

1. 校验referer时要使用正确的正则匹配,不能只校验包含,防止恶意站点使用evilxxxx.bilibili.com之类的referer绕过


2.  拒绝referer为空的请求

验证Referer的方式大多数情况下是作为无法使用Token防御时的缓解方案,虽然这种方式简单易行,不需要过多改变当前系统的代码和逻辑。但是如果是旧系统改造时不得不允许空Referer的请求,那么就会存在利用跨协议提交请求等方式构造空Referer请求绕过防御。此外,因为 Referer 值会记录下用户的访问来源,涉及到用户隐私的问题,目前各大浏览器也都支持设置不发送Referer。如果用户在自己的浏览器设置了发送请求时不再提供 Referer,那么采用这种防御方案的系统便会认为是CSRF攻击拒绝合法用户的请求,所以还需要结合自己的业务场景酌情使用。


4)防御误区:通过CORS防御

既然CSRF必然涉及到跨域请求,那就产生了一个问题:如果正确配置了CORS策略,是否能防御CSRF攻击呢?这个问题的答案就要从什么是CORS说起,跨域资源共享(CORS),是一份浏览器技术的规范,它允许浏览器向跨源服务器,发出XMLHttpRequest 请求,从而克服 AJAX 只能同源使用的限制。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

简单请求是指:

1)方法为:GET、HEAD、POST

2)无自定义头,且Content-Type为以下三种:text/plain、multipart/form-data、application/x-www-form-urlencoded


凡是不同时满足上面两个条件,就属于非简单请求。


当浏览器发现我们的 AJAX 请求是个简单请求,便会自动在头信息中,增加一个 Origin 字段。服务端根据Origin字段来判断是否同意共享资源。同意的话就在返回包头里增加Access-Control-Allow-Origin字段,不同意不加即可。然后浏览器会根据返回包判断是报错还是正常显示内容。大致流程如下图:


从甲方安全视角看漏洞(一)——跨站请求伪造

如果是非简单请求,则浏览器在发送此类请求之前,会增加预检请求,也就是我们熟悉的OPTIONS请求。如果预检请求不通过则不会发送真正的请求。大致流程如下图:


从甲方安全视角看漏洞(一)——跨站请求伪造

知道了CORS的工作原理,我们可以发现,CORS对于CSRF攻击并不具备防御能力,因为:

1.form 表单提交不存在跨域问题,自然也就无法通过CORS进行限制

2.对于简单请求而言,虽然返回结果被浏览器拦截,但写操作的请求仍被服务端接收,攻击已经成功。


5) json格式参数加content-type校验

这种防御方式目前使用较少,主要特点是请求以json格式发送数据,而后端限制content-type的格式只能为application/json。这种方式能防御CSRF的主要原因是:

1.form表单构造post请求时,enctype属性并不支持application/json格式,浏览器在发送请求时会将content-type转为application/x-www-form-urlencoded,从而无法通过content-type限制


2.想要设置自定义Header需要使用XMLHttpRequests或js fetch,但这两种方法都无法完成跨域。因为攻击请求必须修改content-type为application/json,从而攻击请求就变成了非简单请求,这样预检请求并不会得到正确的响应,浏览器也就不会发送后续请求。

这种防御方式在使用时需要注意严格限制content-type。有时候开发人员将请求参数改成json后就认为完成了修复,这时候将content-type改为text/plain后服务端一样会正确响应请求,这样通过轻松构造form表单请求就实现了防御的绕过。


这种防御方式存在着一种古老的绕过方式,但是随着FLASH退出历史舞台,这种防御方式目前也能够起到有效得防御作用。这里简单介绍这种绕过方式,供大家学习记录。


核心思想是利用Flash的跨域与307跳转来绕过HTTP自定义头限制。Adobe Flash 可用于使用 ActionScript 制作 Web 请求,而 ActionScript 还可以用于为 Web 请求设置自定义的 HTTP 头。307跳转又是一种非常特殊的跳转,客户端会向Location里的URL重新发起POST请求,而且会将POST body和HTTP头完完全全重定向到最终的URL,这就为CSRF攻击创造了条件。


具体攻击流程如下:

1.  受害管理员在浏览器中登录到 http://sample.com/

2.  此后,受害者被诱骗导航到 http://attacker/csrf.swf

3.  受害者浏览器加载攻击者准备好的 flash 文件,该文件发起带有payload和自定义 HTTP 头的POST请求给 http://attacker/redirector

4.  攻击者服务器的Redirector服务就发出 HTTP 307 重定向响应。

5.  受害者浏览器收到307响应,会重新发送带有payload和自定义 HTTP 头的POST请求给http://sample.com/adduser,尝试添加用户

6.  业务服务器收到添加用户请求,管理刷新页面就会发现用户已经添加成功

从甲方安全视角看漏洞(一)——跨站请求伪造

这里笔者就使用别人已经造好的轮子(http://cm2.pw/crossdomain)进行测试。可以看到,该请求的服务端对Content-Type进行了校验。


从甲方安全视角看漏洞(一)——跨站请求伪造
接下来就利用http://cm2.pw/crossdomain网站进行绕过,TargetURL填写用来攻击的目标,Header处填写:Content-Type: application/json,RequestData填写:设置密码的json报文体内容,点击Execute 之后,发现合集已经被创建。不难看出,攻击是首先通过返回307状态码重定向到要攻击的url。然后浏览器会自定义的POST body和HTTP头(Content-Type)重定向到要攻击的url,最后就成功创建了合集。

从甲方安全视角看漏洞(一)——跨站请求伪造


从甲方安全视角看漏洞(一)——跨站请求伪造

看到这种绕过方式,可能大家很容易会想到,利用这种方式,岂不是Referer验证的方式也可以轻易的绕过。其实不然,flash并并不允许任意修改HTTP头部字段,referer就是无法修改的字段之一。

 

6)更换用户认证方式

因为CSRF本质是伪造请求携带了保存在Cookie中的信息,所以对使用Session认证机制的应用系统比较容易产生此类问题。如果使用JWT(JSON Web Token)等Token认证的方案,其Token信息一般设置到HTTP头部的,所以可以说对CSRF攻击有天然的防御作用。

 

7)Cookie Samesite属性

Cookie 的SameSite属性通过限制第三方 Cookie,能够有效防止 CSRF 攻击和用户追踪,这个属性可以设置三个值:Strict、Lax、None。


Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。


Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。导航到目标网址的 GET 请求,只包括三种情况:链接、预加载请求和GET 表单。所以,SameSite属性设置了Strict或Lax以后,基本就杜绝了CSRF 攻击。除非被伪造的业务请求是简单的GET请求,那么Lax也不足以防御此类情况。


三、后记

CSRF虽然从危害上来看十分骇人,可以悄无声息的伪造他人或管理员操作。但从实际利用难度来看,风险往往可控。

首先,如果攻击目标为管理员和后台系统,系统请求的参数和结构对于普通用户来说获取存在一定难度。

其次,如果攻击目标为其他用户,重要参数可能无法猜测或重要请求存在二次验证(如转账、修改密码等),可能难以完成有效攻击。

第三,chrome浏览器80版本后,Cookie Samesite的默认值已经变为Lax,其他浏览器目前也逐步修改了默认值。这已经能够防御大多数的CSRF攻击。

综上所述,现阶段CSRF漏洞要想成功利用,还是存在一定的难度,这也可能是2017版OWASP TOP 10移除了CSRF漏洞的原因。随着网络应用系统安全性和用户安全意识的逐步提高,有些安全漏洞可能会逐渐成为历史。而作为一名在甲方企业的安全工作者,平衡安全漏洞实际风险与修复成本之前的关系,往往是工作中的重要一环,如何用最快速最合理的方式规避漏洞风险便是迫切需要掌握的能力,这样才能避免自己在漏洞修复的泥潭里越陷越深。

本文始发于微信公众号(哔哩哔哩安全应急响应中心):从甲方安全视角看漏洞(一)——跨站请求伪造

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: