概述

最近几年都做的是前后端分离中偏后端的项目,最近写了一点点前端的代码,用的是前后端分离的 restful 风格,但是 review 的时候被问起安全问题时,发现自己竟然没有考虑过这方面的问题,所以这里就来思考一下在前后端分离的 restful API 场景下,如何防止 CSRF 和 XSS 这两种常见的网络攻击。

CSRF 和 XSS

关于 CSRF 和 XSS 是什么,其他网络上资源也很多了,在我什么都不懂(其实现在也是什么都不懂)的时候,我觉得《白帽子讲 Web 安全》 这本书让我了解了很多的概念,此外,刚毕业的公司也让我知道了有这个东西的存在:OWASP Top 10 Web Application Security Risks。这里我就结合自己的理解介绍一样这两种攻击分别是什么东西,然后区别是啥,因为我一开始的时候也没理解他们的区别,觉得是同一个东西呀,后面体验测试了一下,就比较了解区别在于哪里了。

从这两个描述来看,这里首先,问题的来源就不一样,一个是你访问了不信任源,一个是你没有访问不信源,但是你访问的地方不够坚固,被攻击了,从而导致你受伤害;但是,这两个造成的后果有点类似,例如,都可以构造让你银行转账的请求,相比之下,XSS 比 CSRF 更难防一些。下面,我们就来聊一下,这两种攻击分别可以怎么来防止。

CSRF 的防御

先来聊一聊 CSRF 的防御,从前面的描述中,可以知道一般情况下,CSRF 的攻击是跨站点的,也就是从 A 访问 B,从这个特性出发,那么一些简单有效的操作方式就是禁止跨站点访问,实用操作有:

白名单过滤并不总是有效,前面说了,大多数情况下有 Origin 和 Referer 字段,那么什么情况下没有这两个字段呢?这依赖于浏览器的实现,一般浏览器(流行的 chrome、firefix、safari、IE)来说,在以下几种情况下是不会有这两个字段的:

当没有这两个字段时,那是不是就没办法了?那倒不是,其实现在很多通用的框架都有提供 CSRF 的防御功能,他们的做法就是:

这个的思路在于如果你跨站的话,那么你是不能拿到我站点上一个动态的东西的:举例来说,你打开恶意网站 A,虽然恶意网站 A 可以向网站 B 发送请求,但是,因为我网站 B 对表单都有 CSRF_TOKEN 校验,你恶意网站 A 无法拿到这个 CSRF_TOKEN,那么就无从发起攻击。

但是,万事无绝对,如果遇到的是类似于 XSS 的同个网站的 CSRF 攻击的话,这个方法也可能会实现,举个例子,如果你的 gmail 收到一封邮件,这个邮件里面就包含针对 gmail 的恶意攻击脚本,你一旦查阅了这个邮件,触发了拉取恶意脚本,并且被浏览器执行了,那么恶意脚本模拟一个修改 gmail 配置的请求发给 gmail,因为这个脚本执行的上下文就在 gmail 上,所以它可以拿到 CSRF_TOKEN,从而跳过你的 CSRF 防御,这样你也被攻击了。

这个时候就是祭出最后的大招了,也是我们平时最烦的一招:验证码,所以平时在输入验证码或者再次输入密码确认的时候,心中的怨气小一些,因为这确实是为了你的安全考虑。

所以对于 restful 场景来说,如果纯粹的无状态,那么肯定是 CSRF 挂逼的,因此,如果我们在 WEB 场景下使用 restful,可以结合 WEB 的 cookie 特性,先进行一层 CSRF 的过滤,后面才进行 restful 的业务处理,总得来说:

一个实际使用案例

图 1:一个实际的案例
图片编辑链接

在这个案例中,Server 返回给浏览器的时候设置了一个 csrf_token 的 Cookie,然后前端收到之后,通过 JS 获取到 Cookie 里面 csrf_token 的值,然后再设置到请求的 Header 中,这样当 Server 收到一个 Post 请求的时候,Server 可以校验 Cookie 中的 csrf_token 的值是否与 Header 中的一致,从而判断这个请求是否是我们自己的 JS 请求的。

Origin 和 Reffeer header

OriginReferer Header 都能用于标识请求是从哪里来的,但是他们也是有一些区别的(废话,如果没区别还需要两个字段吗)。具体的区别在于:

Referer-Policy

XSS 的防御

和 CSRF 不同,XSS 的主要风险来自于对用户输入的防御不足,恶意用户可以将通过精心构造的恶意输入提交到系统,并且被系统以错误的形式展示出来,从而导致其他正常访问系统的客户受到攻击。要说容易防御倒也简单,无非就是对用户的输入进行校验嘛,但是,这个输入其实是广泛的,平时要做到全面是一项非常困难的事情,需要全面的考虑和测试。

XSS 不仅仅是简单的表单文本输入可能存在攻击点,文件上传往往也是一个重要的入口,例如你的后台是 PHP 写的,如果你没有处理好用户上传 php 文件的话,很可能用户上传了一个 PHP 文件,然后下次被访问的时候就变成了执行这个 PHP 文件,导致服务器的攻击,这个更严重。

所以,在 REST 场景下,针对 XSS 的攻击,主要难点还是在于:

  1. 对用户的输入进行格式化处理,确保不会存在攻击的特殊字符;
  2. 对用户输入内容的展示需要额外的注意,确保特殊字符都是被转义过的。

Ref