浏览器安全——CSRF攻击

我们结合一个真实的关于 CSRF 攻击的典型案例来分析下,在 2007 年的某一天,David 无意间打开了 Gmail 邮箱中的一份邮件,并点击了该邮件中的一个链接。过了几天,David 就发现他的域名被盗了。不过几经周折,David 还是要回了他的域名,也弄清楚了他的域名之所以被盗,就是因为无意间点击的那个链接。

那 David 的域名是怎么被盗的呢?

我们结合下图来分析下 David 域名的被盗流程:

  • 首先 David 发起登录 Gmail 邮箱请求,然后 Gmail 服务器返回一些登录状态给 David 的浏览器,这些信息包括了Cookie、Session等,这样在 David 的浏览器中,Gmail 邮箱就处于登录状态了。
  • 接着黑客通过各种手段引诱 David 去打开他的链接,比如hacker.com,然后在hacker.com页面中,黑客编写好了一个邮件过滤器,并通过 Gmail 提供的 HTTP 设置接口设置好了新的邮件过滤功能,该过滤器会将 David 所有的邮件都转发到黑客的邮箱
    中。
  • 最后的事情就很简单了,因为有了 David 的邮件内容,所以黑客就可以去域名服务商那边重置 David 域名账户的密码,重置好密码之后,就可以将其转出到黑客的账户了。

以上就是 David 的域名被盗的完整过程,其中前两步就是 CSRF 攻击。

什么是 CSRF 攻击

CSRF 英文全称是Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。

通常当用户打开了黑客的页面后,黑客有三种方式去实施 CSRF 攻击。

下面我们以极客时间官网为例子,来分析这三种攻击方式都是怎么实施的。这里假设极客时间具有转账功能,可以通过 POST 或 Get 来实现转账,转账接口如下所示:

1
2
3
4
5
# 同时支持 POST 和 Get # 接口
https://time.geekbang.org/sendcoin # 参数
## 目标用户
user ## 目标金额
number

有了上面的转账接口,我们就可以来模拟 CSRF 攻击了。

1. 自动发起 Get 请求

黑客最容易实施的攻击方式是自动发起 Get 请求,具体攻击方式你可以参考下面这段代码:

1
2
3
4
5
6
7
<!DOCTYPE html>
<html>
<body>
<h1> 黑客的站点:CSRF 攻击演示 </h1>
<img src="https://time.geekbang.org/sendcoin?user=hacker&number=100">
</body>
</html>

这是黑客页面的 HTML 代码,在这段代码中,黑客将转账的请求接口隐藏在img标签内,欺骗浏览器这是一张图片资源。当该页面被加载时,浏览器会自动发起img的资源请求,如果服务器没有对该请求做判断的话,那么服务器就会认为该请求是一个转账请求,于是用
户账户上的 100 极客币就被转移到黑客的账户上去了。

2. 自动发起 POST 请求

除了自动发送 Get 请求之外,有些服务器的接口是使用 POST 方法的,所以黑客还需要在他的站点上伪造 POST 请求,当用户打开黑客的站点时,是自动提交 POST 请求,具体的方式你可以参考下面示例代码:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<body>
<h1> 黑客的站点:CSRF 攻击演示 </h1>
<form id='hacker-form' action="https://time.geekbang.org/sendcoin" method=POST>
<input type="hidden" name="user" value="hacker" />
<input type="hidden" name="number" value="100" />
</form>
<script> document.getElementById('hacker-form').submit(); </script>
</body>
</html>

在这段代码中,我们可以看到黑客在他的页面中构建了一个隐藏的表单,该表单的内容就是极客时间的转账接口。当用户打开该站点之后,这个表单会被自动执行提交;当表单被提交之后,服务器就会执行转账操作。因此使用构建自动提交表单这种方式,就可以自动实现跨
站点 POST 数据提交。

3. 引诱用户点击链接

除了自动发起 Get 和 Post 请求之外,还有一种方式是诱惑用户点击黑客站点上的链接,这种方式通常出现在论坛或者恶意邮件上。黑客会采用很多方式去诱惑用户点击链接,示例代码如下所示:

1
2
3
4
5
6
<div>
<img width=150 src=http://images.xuejuzi.cn/1612/1_161230185104_1.jpg />
<a href="https://time.geekbang.org/sendcoin?user=hacker&number=100" taget="_blank">
点击下载美女照片
</a>
</div>

这段黑客站点代码,页面上放了一张美女图片,下面放了图片下载地址,而这个下载地址实际上是黑客用来转账的接口,一旦用户点击了这个链接,那么他的极客币就被转到黑客账户上了。

以上三种就是黑客经常采用的攻击方式。如果当用户登录了极客时间,以上三种 CSRF 攻击方式中的任何一种发生时,那么服务器都会将一定金额的极客币发送到黑客账户。

到这里,相信你已经知道什么是 CSRF 攻击了。和 XSS 不同的是,CSRF 攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户的登录状态来实施攻击。

如何防止 CSRF 攻击

了解了 CSRF 攻击的一些手段之后,我们再来看看 CSRF 攻击的一些“特征”,然后根据这些“特征”分析下如何防止 CSRF 攻击。下面是我总结的发起 CSRF 攻击的三个必要条件:

  • 第一个,目标站点一定要有 CSRF 漏洞;
  • 第二个,用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态;
  • 第三个,需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。

满足以上三个条件之后,黑客就可以对用户进行 CSRF 攻击了。这里还需要额外注意一点,与 XSS 攻击不同,CSRF 攻击不会往页面注入恶意脚本,因此黑客是无法通过 CSRF 攻击来获取用户页面数据的;其最关键的一点是要能找到服务器的漏洞,所以说对于 CSRF 攻击我们主要的防护手段是提升服务器的安全性。

要让服务器避免遭受到 CSRF 攻击,通常有以下几种途径。

黑客会利用用户的登录状态来发起 CSRF 攻击,而Cookie正是浏览器和服务器之间维护登录状态的一个关键数据,因此要阻止 CSRF 攻击,我们首先就要考虑在Cookie上来做文章。

通常 CSRF 攻击都是从第三方站点发起的,要防止 CSRF 攻击,我们最好能实现从第三方站点发送请求时禁止Cookie的发送,因此在浏览器通过不同来源发送 HTTP 请求时,有如下区别:

  • 如果是从第三方站点发起的请求,那么需要浏览器禁止发送某些关键Cookie数据到服务器;
  • 如果是同一个站点发起的请求,那么就需要保证Cookie数据正常发送。

Cookie中的SameSite属性正是为了解决这个问题的,通过使用SameSite可以有效地降低 CSRF 攻击的风险。

SameSite是怎么防止 CSRF 攻击的呢?

在 HTTP 响应头中,通过set-cookie字段设置Cookie时,可以带上SameSite选项,如下:

1
set-cookie: 1P_JAR=2019-10-20-06; expires=Tue, 19-Nov-2019 06:36:21 GMT; path=/

SameSite选项通常有Strict、LaxNone三个值。
Strict最为严格。如果SameSite的值是Strict,那么浏览器会完全禁止第三方Cookie。简言之,如果你从极客时间的页面中访问 InfoQ 的资源,而 InfoQ 的某些Cookie设置了SameSite = Strict的话,那么这些Cookie是不会被发送到 InfoQ 的服
务器上的。只有你从 InfoQ 的站点去请求 InfoQ 的资源时,才会带上这些Cookie

Lax相对宽松一点。在跨站点的情况下,从第三方站点的链接打开和从第三方站点提交Get方式的表单这两种方式都会携带Cookie。但如果在第三方站点中使用 Post 方法,或者通过img、iframe等标签加载的 URL,这些场景都不会携带Cookie

而如果使用None的话,在任何情况下都会发送Cookie数据。

对于防范 CSRF 攻击,我们可以针对实际情况将一些关键的Cookie设置为Strict或者Lax模式,这样在跨站点请求时,这些关键的Cookie就不会被发送到服务器,从而使得黑客的 CSRF 攻击失效。

2. 验证请求的来源站点

接着我们再来了解另外一种防止 CSRF 攻击的策略,那就是在服务器端验证请求来源的站点。由于 CSRF 攻击大多来自于第三方站点,因此服务器可以禁止来自第三方站点的请求。那么该怎么判断请求是否来自第三方站点呢?

这就需要使用 HTTP 请求头中的RefererOrigin属性了。

Referer是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址。比如我从极客时间的官网打开了 InfoQ 的站点,那么请求头中的Referer值是极客时间的 URL,如下图:

虽然可以通过Referer告诉服务器 HTTP 请求的来源,但是有一些场景是不适合将来源 URL 暴露给服务器的,因此浏览器提供给开发者一个选项,可以不用上传Referer值。

但在服务器端验证请求头中的Referer并不是太可靠,因此标准委员会又制定了Origin属性,在一些重要的场合,比如通过XMLHttpRequest、Fecth发起跨站请求或者通过 Post 方法发送请求时,都会带上Origin属性,如下图:

从上图可以看出,Origin属性只包含了域名信息,并没有包含具体的 URL 路径,这是OriginReferer的一个主要区别。在这里需要补充一点,Origin的值之所以不包含详细路径信息,是有些站点因为安全考虑,不想把源站点的详细路径暴露给服务器。

因此,服务器的策略是优先判断Origin,如果请求头中没有包含Origin属性,再根据实际情况判断是否使用Referer值。

3. CSRF Token

除了使用以上两种方式来防止 CSRF 攻击之外,还可以采用 CSRF Token 来验证,这个流程比较好理解,大致分为两步。
第一步,在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。你可以参考下面示例代码:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="https://time.geekbang.org/sendcoin" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9aj" />
<input type="text" name="user"> <input type="text" name="number">
<input type="submit">
</form>
</body>
</html>

第二步,在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。

总结

要发起 CSRF 攻击需要具备三个条件:目标站点存在漏洞、用户要登录过目标站点和黑客需要通过第三方站点发起攻击。

防止 CSRF 攻击主要有三种方式:充分利用好CookieSameSite属性、验证请求的来源站点和使用 CSRF Token。

这三种方式需要合理搭配使用,这样才可以有效地防止 CSRF 攻击。

页面安全问题的主要原因就是浏览器为同源策略开的两个“后门”:一个是在页面中可以任意引用第三方资源,另外一个是通过 CORS 策略让XMLHttpRequestFetch去跨域请求资源。

为了解决这些问题,我们引入了 CSP 来限制页面任意引入外部资源,引入了HttpOnly机制来禁止XMLHttpRequest或者Fetch发送一些关键Cookie,引入了SameSiteOrigin来防止 CSRF 攻击。

打赏
  • Copyrights © 2017-2023 WSQ
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信