常见web安全漏洞详解(二):CSRF

916 阅读7分钟

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

前言

跨站请求伪造(Cross-site request forgery),是常见的web安全漏洞之一,早在2000年被国外的安全人员发现,此后,国内外的多个大型社区和交互网站都分别爆出因CSRF漏洞而造成的安全事故,一直到现在,互联网上的许多站点仍对此毫无防备,因此CSRF又被称网络安全界的“沉睡的巨人”。

原理

简单地说,就是攻击者先让受害者登录一个需要权限验证的网站,当用户完成验证后,权限凭证会被保存到本地,下次发送请求时会一同发送到服务器作为验证信息。然后诱导受害者打开一个攻击者编写的恶意网站,这个恶意网站会发送相关请求去执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品),利用受害者之前保存的权限凭证,绕过服务端验证,从而完成攻击。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

image.png

一个典型的CSRF攻击有着如下的流程:

  • 受害者登录网站A,并保留了登录凭证(Cookie)。
  • 攻击者诱导受害者访问恶意网站B。
  • 恶意网站B 向 网站A的服务器 发送了一个请求,浏览器会默认携带ACookie
  • 服务器接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  • A以受害者的名义执行了攻击者要求的操作。
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让网站A执行了自己定义的操作。

攻击类型

CSRF攻击手段千变万化,但是万变不离宗,这里列举了几个典型的进行介绍。

GET类型

GET类型的CSRF攻击非常简单,通常攻击者会将请求URL放置在能自动触发GET请求的位置,如img标签的src:

<img src="http://www.bank.com/withdraw?toBankId=11&money=1000">

用户首先登录了银行账户,然后被诱导打开了一个包含上述标签的页面,页面解析加载,发送请求,1000大洋便不翼而飞。

POST类型

这种类型的攻击通常利用一个自动提交的表单,以2007GmailCSRF安全事故为例:

<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,所以这个请求发送时,会携带相关的登录凭证,服务器收到请求后,根据携带的登录凭证,认为该请求合法,于是攻击者便能劫持用户账号接下来的所有邮件。

链接类型

链接类型的CSRF并不常见,比起上述两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式出现,攻击者通常会以比较雷人的词语诱骗用户点击,例如:

<a href="http://www.bank.com/withdraw?toBankId=11&money=1000" taget="_blank"> 震惊!女人听了流泪,男人听了心碎,竟是因为...>

由于用户之前已经登录账号,相关权限凭证已经被保存,因此请求能被服务器响应,攻击达成。

界面劫持

界面操作劫持攻击实际上是一种基于视觉欺骗的web会话劫持攻击,核心在于使用了标签中的透明属性,通过在网页的可见输入控件上覆盖一个不可见的框(iframe),使得用户误以为在操作可见控件,而实际上用户的操作行为被其不可见的框所劫持,执行不可见框中的恶意代码,达到窃取信息,控制会话,植入木马等目的。常见的界面劫持方式有:点击劫持拖放劫持触屏劫持

这里以点击劫持为例进行说明:

无标题-2021-08-28-1015.png

当前界面包含了一个不透明的iframe,指向一个恶意界面,同时上面覆盖了一个可见的按钮,但是这个按钮设置的css属性point-events使得事件可以透传至iframe中,而iframe中的页面也有一个按钮处于与外部按钮相同的位置,因此,点击这个可视的按钮,实际上点击的是这个不可见的iframe中的按钮,这样使得iframe内的恶意代码被不知不觉地手动执行了。

防御

基于上述分析,CSRF攻击就是冒充用户进行操作,发起的请求都是伪造的,而发起请求的地址是第三方地址,也就是说请求来源都是异常的,那么我们就可以在服务端通过过滤请求来源来进行防御。

Origin + Referrer 同源检测

Origin 指示了请求来自于哪个站点,Referer 表明当前请求是从哪个页面发出的,这两个首部字段功能类似,服务器可以使用它们来识别访问来源。

浏览器在发起请求时,大多数情况会自动带上这两个字段,并且不能由前端自定义内容,服务端可以根据它们判断来源是否可靠,否则直接阻止。

csrf-token

CSRF攻击中,攻击者无法直接窃取用户权限信息,只是通过手段冒充、诱导用户来发送请求,而服务器无法区别请求是否出自用户的个人意愿。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开。

例如,服务端返回一个页面时,把token嵌入到页面元素中:

<form action="http://bank.example/withdraw" method=POST>
    <input name="account" value="" />
    <input name="password" value="" />
    <input type="hidden" name="csrf-token" value="@#%^^%#%#%$#%#" />
</form>

在表单元素内埋入一个隐藏的input标签,标签的值就是token值,当表单提交时,这个token会一起发送到服务器进行校验,来决定服务端是否响应这个请求的凭证。

Samesite Cookie

为了从源头上杜绝CSRF攻击,谷歌起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite字段,用来标明这个Cookie是个同站 Cookie,同站Cookie只能作为第一方Cookie,不能作为第三方CookieSamesite 有两个属性值,分别是 StrictLax

Samesite=Strict

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

Set-Cookie: foo=1; 
Samesite=Strict Set-Cookie: bar=2; 
Samesite=Lax Set-Cookie: baz=3

在网站A下发起对网站B的任意请求,foo 这个 Cookie 都不会被装载入 Cookie 请求头中,但 bar 会。举个实际的例子就是,假如淘宝用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从搜索引擎搜索页面甚至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,因为淘宝的服务器不会接受到那个 Cookie,其它网站发起的对淘宝的任意请求都不会带上那个 Cookie

Samesite=Lax

这种称为宽松模式,较Strict少了一些限制:假如一个请求是这样的:改变了当前页面或者打开了新页面,且同时是个GET请求,那么这个Cookie可以作为第三方Cookie。比如说网站A设置了以下Cookie字段:

Set-Cookie: foo=1; 
Samesite=Strict Set-Cookie: bar=2; 
Samesite=Lax Set-Cookie: baz=3;

当用户从站点A点击链接进入站点B时,foo不会被包含在 Cookie字段中,但是 bazbar 会被包含,也就是说用户在不同网站之间通过链接跳转是不受同站cookie的影响。