CSRF攻击与预防

一个案例

有一天,小明同学在刷着Gmail邮件。有一封邮件引起了小明的注意:

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

小明抱着好奇的态度点了进去。结果,这只是一个什么都没有的空白页面,小明失望的关闭了页面。一切似乎什么都没有发生

在这平静的外表之下,黑客的攻击已然得手。小明的Gmail中,被偷偷设置了一个过滤规则,这个规则使得所有的邮件都会被自动转发到hacker@hackermail.com

不久之后的一天,小明突然发现自己的域名已经被转让了。小明仔细查了下域名的转让,对方是拥有自己的验证码的,而域名的验证码只有自己能收到,只存在于自己的邮箱里面。小明回想起那天奇怪的链接,打开后重新查看了“空白页”的源码:

<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的后台接收到请求,验证了cookie确实是小明的,就会以小明的权限去执行这个请求对应的操作。

此事件原型是2007年Gmail的CSRF漏洞:

Warning: Google Gmail security failure

什么是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/payfor?money=100&to=xiaoming" alt="">

受害者在访问含有这个img的攻击页面后,浏览器会自动带着bank.example的cookie向http://bank.example/payfor?money=100&to=xiaoming 发出一次HTTP请求。bank.example对请求进行验证之后确认了cookie是正确的,就会以受害者的身份去执行这个请求对应的操作。

POST类型的CSRF

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

<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>

POST类型的攻击通常比GET要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST上面。访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。

链接类型的CSRF

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

<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">

 重磅消息!!

 <a/>

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

CSRF的特点

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
  • 整个过程攻击者并不能获取到受害者的cookie,仅仅是“冒用”。

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

CSRF攻击的预防

上面讲了CSRF的两个特点:

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

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

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

1.同源检测

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

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

  • Origin Header
  • Referer Header

这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header中的域名,确定请求的来源域。

但是,某些情况下请求不会携带这两个header:

  • IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头

  • 302重定向: 在302重定向之后Origin不包含在重定向的请求中

  • 通过在请求头中设置Refer Policy,可以隐藏refer
  • IE6、7下使用window.location.href=url和window.open都会缺失refer
  • HTTPS页面跳转到HTTP页面

因此,当Origin和Referer都不存在时,建议直接进行阻止。

但是要注意,当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击。所以在阻止不明来源的请求时需要过滤掉页面请求情况。通常Header符合以下情况:

Accept: text/html

Method: GET

缺点

  • CSRF大多数情况下来自第三方域名,但并不能排除本域发起。如果攻击者有权限在本域发布评论(含链接、图片等,统称UGC),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。

2.CSRF Token

由于在CSRF攻击中,攻击者无法直接窃取到用户的信息,仅仅是冒用。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。步骤如下:

  1.  将CSRF Token输出到页面中
    首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,一般Token都包括随机字符串和时间戳的组合。
     
  2. 页面提交的请求携带这个Token
    对于GET请求,Token将附在URL之后,这样URL 就变成 http://url?csrftoken=tokenvalue ;或者放在请求头中:
  3. 而对于 POST 请求来说,可以放在请求体或者请求头中。
  4. 服务器验证Token是否正确
    解密Token  ->  对比随机字符串以及时间戳,如果随机字符串一致且时间未过期,那么这个Token就是有效的。


通常,开发人员只需为当前会话生成一次Token。在初始生成此Token之后,该值将存储在会话中,并用于每个后续请求,直到会话过期。

分布式校验

现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据。所以,需要把session存储在公共空间里比如数据库。但是每次请求都需要读取数据库,比较耗时。

所以,目前很多网站采用Encrypted Token Pattern方式,比如JWT(JSON Web Token)。这种方法的Token是一个计算出来的结果,而非随机生成的字符串。这样在校验时无需再去读取存储的Token,只用再次计算一次即可。

这种Token的值通常是使用UserID、时间戳和随机数,通过加密的方法生成。这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。

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

注:验证码和密码其实也可以起到CSRF Token的作用哦,而且更安全

3.双重cookie验证

由于单纯的csrf只能让请求中带有cookie,但是并不能读取cookie加入到POST或URL中。因此前端在发送请求时可以读取cookie并将它加入到url或者请求体中。

双重Cookie采用以下流程:

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

不过此方法并没有大规模应用,其在大型网站上的安全性还是没有CSRF Token高。

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

  • 如果用户访问的网站为me.a.com,而后端的api域名为api.a.com。那么在me.a.com下,前端拿不到api.a.com的Cookie,也就无法完成双重Cookie认证。
  • 于是这个认证Cookie必须被种在a.com下,这样每个子域都可以访问。
  • 任何一个子域都可以修改a.com下的Cookie。
  • 某个子域名存在漏洞被XSS攻击(例如xxx.a.com),则攻击者可以在这个子域下获取a.com的cookie,向me.a.com发起CSRF攻击。

优点:

  • 无需使用Session,适用面更广,易于实施。
  • 验证信息储存于客户端中,不会给服务器带来压力。

缺点:

  • 难以做到子域名的隔离。

4.Samesite Cookie属性

通过设置Set-Cookie响应头的Samesite属性来防止CSRF攻击。

Samesite=Strict

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

Set-Cookie: aaa=1; Samesite=Strict

Set-Cookie: ccc=3

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

Samesite=Lax

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

Set-Cookie: eee=1; Samesite=Lax

Set-Cookie: ccc=3

当用户从 a.com 点击链接进入 b.com 时,eee和ccc这两个cookie都会被包含在Cookie请求头中,也就是说用户在不同网站之间通过链接跳转是不受影响了。

我们应该如何使用SamesiteCookie

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

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

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

另外一个问题是Samesite的兼容性不是很好,现阶段除了从新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持,现阶段看来暂时还不能普及。

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

缺点

  • Samesite=Strict时,安全性较高,用户体验不友好
  • Samesite=Lax时,安全性较低
  • 兼容性不是很好
  • 不支持子域

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的网络攻击,击者通过利用用户在已登录的网站上的身份验证凭证,向目标网站发送恶意请求。攻击原理如下: 1. 用户在已登录的网站A上获取了一个身份验证凭证,通常是一个cookie。 2. 攻击者诱导用户访问恶意网站B,网站B上包含了针对网站A的恶意请求。 3. 用户在不知情的情况下,浏览器会自动发送之前获取的身份验证凭证到网站A,执行恶意请求。 预防CSRF攻击的方法有以下几种: 1. 验证来源:服务器端验证请求的来源是否合法,可以通过检查Referer头或使用CSRF Token来实现。Referer头验证可防止一部分攻击,但可能存在被伪造的风险;而CSRF Token是一种随机生成的令牌,将其嵌入到表单或请求参数中,并在服务器端进行验证,可以有效防止CSRF攻击。 2. SameSite属性:通过设置SameSite属性为Strict或Lax,限制cookie只能在同一站点内发送,防止跨站点请求。 3. 双重提交Cookie验证:服务器在生成页面时,在返回给客户端的表单中添加一个隐藏字段,该字段包含了与用户cookie关联的数据。在接收到表单提交请求时,服务器会验证该字段的合法性。 4. 随机化请求参数:在每次请求中都生成一个随机的请求参数,并将其嵌入到请求中,服务器在接收到请求时验证这个参数的合法性。 5. 使用验证码:对于重要操作,可以要求用户输入验证码,增加安全性。 综合采用以上多种预防措施可以有效地减少CSRF攻击的风险。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值