浏览器安全:34 | CSRF攻击:陌生链接不能随便点
前言:该篇说明:请见 说明 —— 浏览器工作原理与实践 目录
上一篇文章中讲到了 XSS 攻击,XSS 的攻击方式是黑客往用户页面中注入恶意脚本,然后再通过恶意脚本将用户页面的数据上传到黑客服务器上,最后黑客再利用这些数据进行一些恶意操作。XSS 攻击能带来很大的破坏性,不过另外一种类型的攻击也不容忽视,它就是今天要讲的 CSRF 攻击。
相信经常能听到这句话:“别点那个链接,小心有病毒!”,点击一个链接怎么就染上病毒?
下面结合一个真实的关于 CSRF 攻击的典型案例来分析下,在 2007 年的某一天,David 无意间打开了 邮箱中的一份邮件,并点击了该邮件中的一个链接。过了几天,就发现他的域名被盗了。不过几经周折还是要回了他的域名,也弄清楚了域名之所以被盗,就是因为无意间点击的那个链接。
那域名是怎么被盗的?
结合下图来分析下被盗的流程:
域名被盗流程
- 首先 David 发起登录邮箱请求,然后 Gmail 服务器返回一些登录状态给浏览器,这些信息包括了 Cookie、Session 等,这样在浏览器中,Gmail 邮箱就处于登录状态了。
- 接着黑客通过各种手段引诱他去打开链接,比如hacker.com,然后在 hacker.com 页面中,黑客编写好了一个邮件过滤器,并通过 Gmail 提供的 HTTP 设置接口设置好了新的邮件过滤功能,该过滤器会将他所有的邮件都转发到黑客的邮箱中。
- 最后的事情很简单了,因为有了 David 的邮件内容,所以黑客就可以去域名服务商那边重置David 域名账户的密码,重置好密码之后,就可以将其转出到黑客的账户了。
以上就是David 的域名被盗的整个过程,其中前两步就是今天要讲的 CSRF 攻击。
什么是 CSRF 攻击
CSRF 全称 Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求,简单来说,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。
通常当用户打开了黑客的页面后,黑客有三种方式去实施 CSRF 攻击。
下面以极客时间官网为例子,来分析这三种攻击方式都是怎么实施的。假设极客时间具有转账功能,可以通过 POST 或 GET 来实现转账,转账接口如下:
#同时支持POST和Get #接口 https://time.geekbang.org/sendcoin #参数 ##目标用户 user ##目标金额 number
有了上面的转账接口,就可以来模拟 CSRF 攻击了。
1. 自动发起 GET 请求
黑客最容易实施的攻击方式是自动发起 GET 请求,具体攻击方式可以如下代码:
<!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 请求,具体的方式可以参考如下代码:
<!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 请求外,还有一种方式是诱惑用户点击黑客站点上的链接,这种方式通常出现在论坛或恶意邮件上。黑客会采用很多方式去诱惑用户点击链接,示例代码:
<div> <img width=150 src=http://images.xuejuzi.cn/1612/1_161230185104_1.jpg> </img> </div> <div> <a href="https://time.geekbang.org/sendcoin?user=hacker&number=100" taget="_blank"> 点击下载美女照片 </a> </div>
这段黑客站点代码,页面上放了一张美女图片,下面放了图片下载地址,而这个下载地址实际上是黑客用来转账的接口,一旦用户点击了这个链接,那用户的极客币就被转到黑客账户上了。
以上三种方式就是黑客经常采用的攻击方式。如果当用户登录了极客时间,以上三种 CSRF 攻击方式中的任何一种发生时,那服务器都会将一定金额的极客币转到黑客账户。
现在知道什么是 CSRF攻击了。和 XSS 不同的是,CSRF攻击不需要将恶意代码注入用户的页面,仅仅是利用服务器的漏洞和用户登录状态来实施攻击。
如何防止 CSRF 攻击
了解了 CSRF 攻击的手段后,再来看看 CSRF 攻击的 一些“特征”,然后根据这些“特征”分析下如何防止 CSRF 攻击。
CSRF 攻击的三个必要条件:
1、目标站点一定要有 CSRF 漏洞;
2、用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态;
3、需要用户打开一个第三方站点,可以是黑客的站点,也可以是一些论坛。
满足以上三点,黑客就可以对用户进行 CSRF 攻击了。这里还需要额外注意一点,与 XSS 攻击不同,CSRF 攻击不会往页面注入恶意脚本,因此黑客是无法通过 CSRF 攻击来获取用户页面数据;其最关键一点是要能找到服务器的漏洞,所以说对于 CSRF 攻击,主要的防护手段是提升服务器的安全性。
要让服务器避免遭受到 CSRF 攻击,通常有以下几种途径。
1. 充分利用好 Cookie 的 SameSite 属性
现在知道黑客会利用用户的登录状态来发起 CSRF 攻击,而 Cookie 正是浏览器和服务器之间维护登录状态的一个关键数据,因此要阻止 CSRF 攻击,首先就要考虑在 Cookie 上做文章。
通常 CSRF 攻击都是从第三方站点发起的,要防止 CSRF 攻击,最好能实现从第三方站点发送请求时禁止 Cookie 的发送,因此在浏览器通过不同来源发送 HTTP 请求时,有如下区别:
- 如果是从第三方站点发起的请求,需要浏览器禁止发送某些关键 Cookie 数据到服务器;
- 如果是同一个站点发起的请求,就需要保证 Cookie 数据正常发送。
而 Cookie 的 SameSite 属性正是为了解决这个问题的,通过使用 SameSite 可以有效降低 CSRF 攻击的风险。
那 SameSite 是怎么防止 CSRF 攻击的?
在 HTTP 响应头中,通过 set-cookie 字段设置 Cookie时,可以带上 SameSite 选项,如下:
set-cookie: 1P_JAR=2019-10-20-06; expires=Tue, 19-Nov-2019 06:36:21 GMT; path=/; domain=.google.com; SameSite=none
SameSite 选项通常有 Strict、Lax 和 Node 三个值
- Strict 最为严格。如果 SameSite 的值是 Strict,那浏览器会完全禁止第三方 Cookie 。简言之,如果从极客时间的页面访问 InfoQ 的资源,而 InfoQ 的某些 Cookie 设置了 SameSite = Strict 的话,那这些 Cookie 是不会被发送到 InfoQ 的服务器上的①。只有从 InfoQ 的站点去请求 InfoQ 的资源,才会带上这些 Cookie。
- Lax 相对宽松点。在跨站点的情况下,从第三方站点的链接打开和从第三方站点提交 Get 方式的表单这两种方式都会携带 Cookie,但如果在第三方站点中使用 Post 方法,或者通过 img、iframe 等标签加载的 URL,这些场景都不会携带 Cookie。
- None,在任何情况下都会发送 Cookie 数据。
关于 SameSite 的具体使用方式,可以参考链接:https://web.dev/samesite-cookies-explained/
对于防范 CSRF 攻击,可以针对实际情况将一些关键的Cookie 设置为 Strict 或 Lax模式,这样在跨站点请求时,这些关键的 Cookie 就不会被发送到服务器,从而使得黑客的 CSRF 攻击失效。
2. 验证请求的来源站点
再来了解另一种防止 CSRF 攻击的策略:在服务器端验证请求来源的站点。由于 CSRF 攻击大多来自第三方站点,因此服务器可以禁止来自第三方站点的请求。那该如何判断请求是否来自第三方站点?
这就需要介绍 HTTP 请求头中的 Referer 和 Origin 属性了。
Referer 是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址。比如从极客时间官网打开了 InfoQ 的站点,那请求头中的 Referer 值是极客时间的 URL,如下图:
HTTP 请求头中的 Referer 引用
虽然可以通过 Referer告诉服务器 HTTP 请求的来源,但是有一些场景是不适合将来源URL暴露给服务器的,因此浏览器提供给开发者一个选项,可以不用上传 Referer 值,具体可参考 Referer Policy。
但在服务器端验证请求头中的 Referer 并不是太可靠,因此标准委员会又指定了 Origin 属性,在一些重要场合,比如通过 XMLHTTPRequest、Fetch 发起跨站点请求或通过 Post 方法发送请求时,都会带上 Origin 属性,如下:
Post 请求时的 Origin 信息
从上图可以看出,Origin 属性只包含了域名信息,并没有包含具体的 URL 路径,这是 Origin 和 Referer 的一个主要区别。在这里需要补充一点,Origin 的值之所以不包含详细路径信息,是有些站点因为安全考虑,不想把源站点的详细路径暴露给服务器。
因此,服务器的策略是优先判断 Origin,如果请求头中没有包含 Origin 属性,再根据实际情况判断是否使用 Referer 值。
3. CSRF Token
除了使用以上两种方式防止 CSRF 攻击外,还可以采用 CSRF Token 来验证,这个流程比较好理解,大致分为两步:
第一步:在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。参考下图:
<!DOCTYPE html> <html> <body> <form action="https://time.geekbang.org/sendcoin" method="POST"> <input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn"> <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 攻击需要具备三个特征:目标站点存在漏洞、用户登录过目标站点和黑客需要通过第三方站点发起攻击。
根据这三个必要条件,又介绍了该如何防止 CSRF 攻击,具体来讲主要有三种方式:充分利用 Cookie 的 SameSite 属性、验证请求的来源站点和使用 CSRF Token。这三种方式需要合理搭配使用,这样才能有效地防止 CSRF 攻击。
再结合前面两篇文章,可以得出页面安全问题的主要原因是 浏览器为同源策略开的两个“后门”:一个是在页面中可以任意引用第三方资源,两外一个是通过 CORS 策略让 XMLHTTPRequest 和 Fetch 去跨域请求资源。
为了解决这些问题,引入了 CSP 来限制页面任意引入外部资源,引入了 HttpOnly 机制来禁止 XMLHttpRequest 或 Fetch 发送一些关键 Cookie,引入了 SameSite 和 Origin 来防止 CSRF 攻击。
通过这三篇文章的分析,应该能搭建 Web 页面安全的知识体系网络了。有了这张网络,就可以将 Http 请求头和响应头中各种安全相关的字段关联起来,比如 Cookie 中的一些字段,还有 X-Frame-Options、X-Content-Type-Options、X-XSS-Protection 等字段,也可以将 CSP、CORS 这些知识点关联起来。当然这些并不是浏览器安全的全部,后面两篇文章还会介绍浏览器系统安全和浏览器网络安全两大块内容。
思考题
什么是 CSRF 攻击?在开发项目过程中应该如何防御 CSRF 攻击?
个人总结:
CSRF 攻击:
用户登录网1,网1 中存在诱导用户点击的黑客链接,用户点击链接进入第三方站点,又第三方站点(黑客站点)利用当前用户的登录状态发起请求(do something)。
如何防御:三点
1. set-cookie 中的 SameSite
2.服务器判断请求来源,Origin / Referer
3. CSRF Token 验证
记录
1、“简言之,如果你从极客时间的页面中访问 InfoQ 的资源,而 InfoQ 的某些 Cookie 设置了 SameSite = Strict 的话,那么这些 Cookie 是不会被发送到 InfoQ 的服务器上的”,这里是不是我理解错了还是写错了。应该是不会发送到极客时间的服务器上,或者说极客时间的某些Cookie设置了SameSite = Strict吧。
作者回复:我把整个流程写一遍:
首先假设你发出登录InfoQ的站点请求,然后在InfoQ返回HTTP响应头给浏览器,InfoQ响应头中的某些set-cookie字段如下所示:
set-cookie: a_value=avalue_xxx; expires=Thu, 21-Nov-2019 03:53:16 GMT; path=/; domain=.infoq.com; SameSite=strict
set-cookie: b_value=bvalue_xxx; expires=Thu, 21-Nov-2019 03:53:16 GMT; path=/; domain=.infoq.com; SameSite=lax
set-cookie: c_value=cvaule_xxx; expires=Thu, 21-Nov-2019 03:53:16 GMT; path=/; domain=.infoq.com; SameSite=none
set-cookie: d_value=dvaule_xxxx; expires=Thu, 21-Nov-2019 03:53:16 GMT; path=/; domain=.infoq.com;
可以看出,
a_value的SameSite属性设置成了strict,
b_value的SameSite属性设置成了lax
c_value的SameSite属性值设置成了none
d_value没有设置SameSite属性值
好,这些Cookie设置好之后,当你再次在InfoQ的页面内部请求InfoQ的资源时,这些Cookie信息都会被附加到HTTP的请求头中,如下所示:
cookie: a_value=avalue_xxx;b_value=bvalue_xxx;c_value=cvaule_xxx;d_value=dvaule_xxxx;
但是,假如你从time.geekbang.org的页面中,通过a标签打开页面,如下所示:
<a href="https://www.infoq.cn/sendcoin?user=hacker&number=100">点我下载</a>
当用户点击整个链接的时候,因为InfoQ中a_vaule的SameSite的值设置成了strict,那么a_vaule的值将不会被携带到这个请求的HTTP头中。
如果time.geekbang.org的页面中,有通过img来加载的infoq的资源代码,如下所示:
<img src="https://www.infoq.cn/sendcoin?user=hacker&number=100">
那么在加载infoQ资源的时候,只会携带c_value,和d_value的值。
2、老师,我想请问一下,在浏览器打开第三方站点是如何拿到极客时间站点cookie的?第三方站点和极客时间的站点不同,存在同源策略,所以转账请求验证cookie也是不通过的,那么CSRF是如何攻击的呢?
层主的问题梳理一下是两个点:1.第三方网站是如何拿到cookie的 2.同源策略为什么不能阻止CSRF。 同源策略并没有完全限制网站不能使用非同源的资源,比如引用第三方script文件,引用第三方CSS文件等,同样的,也没有限制一些跨域写操作,比如表单提交。因此,光靠同源策略不能阻止CSRF。明白了可以在第三方站点成功发送跨域请求这一点之后,浏览器会自动带上请求的那个站点的cookie。
3、有个疑问:
same origin policy不是确保了不同域名时间不可以访问数据的吗? 那第三方站点如何拿到cookie和session?
作者回复:
如果是CSRF攻击,那么黑客是拿不到受害者站点数据的。
但是黑客会在他的A站点中调用受害者B站点的http接口,这些接口可以是转账,删帖或者设置等。
这个过程中你需要注意一点,在黑客A站点中调用受害者B站点的http接口时,默认情况下,浏览器依然会把受害者的Cookie等信息数据发送到受害者的B站点,【注意这里并不是黑客的A站点】。
如果B站点存在漏洞的话,那么黑客就会攻击成功,比如将受害者的金币转出去了!
4、CSRF TOKEN类似于cookie, 都是存储用户信息,而此用户信息只存储在当前你请求的站点,而不是浏览器,所以不同的标签页面,或不同次的请求是不共享此用户信息的,所以规避了cookie所带来的的漏洞,老师,我这样理解对?
作者回复:
是的,针对于请求的,比如同一个浏览器打开两个相同页面,那么服务器为这两个页面生成的csrf token都是不同的