乐趣区

前端安全系列CSRF篇

系列文章:

前端安全系列:XSS 篇
前端安全系列:CSRF 篇

CSRF 介绍

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本,但它与 XSS 非常不同,XSS 利用站点内的信任用户,而 CSRF 则通过伪装成受信任用户的请求来利用受信任的网站。攻击通过在授权用户访问的页面中包含链接或者脚本的方式工作

CSRF 攻击

一个典型的 CSRF 攻击流程大概如下:

  1. 用户登录 a.com保留登录信息
  2. 攻击者 引诱用户访问 b.com
  3. b.com在用户不知情的情况下向 a.com 发送请求并携带用户的登录信息
  4. a.com接收请求验证登录信息通过执行某些恶意操作
  5. 攻击者在用户不知情的情况下冒充用户的身份完成了攻击.

攻击方式:

  • 攻击者的网站
  • 有文件上传漏洞的网站
  • 第三方论坛, 博客等网站
  • 目标网站自身的漏洞

相对 XSS 攻击,CSRF攻击不太一样

  • 一般攻击发起点不在目标网站, 而是被引导到第三方网站再发起攻击, 这样目标网站就无法防止
  • 攻击者不能获取到用户 Cookies, 包括子域名, 而是利用 Cookies 的特性冒充用户身份进行攻击
  • 通常是跨域攻击, 因为攻击者更容易掌握第三方网站而不是只能利用目标网站自身漏洞
  • 攻击方式包括图片,URL,CORS, 表单, 甚至直接嵌入第三方论坛, 文章等等, 难以追踪

常见的 CSRF 攻击类型

GET 请求

例如利用隐藏图片自动发起一个 HTTP 请求, 会自动附带用户 cookies

<img style="width:0;" src="https://www.test.com/xxx" />

POST 请求

例如利用隐藏表单自动提交

<form action="https://www.test.com/xxx" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
</form>
<script> document.forms[0].submit(); </script> 

URL 攻击

比较常见的利诱广告方式或者冒充 QQ 病毒警告等引诱用户自己点击

<a href="https://www.test.com/xxx" taget="_blank">
  一刀 9999 级, 神级装备, 顶级神宠, 开服就有!!<a/>

防御

针对 CSRF 的特点, 我们可以制定策略

限制访问名单

同源检测

HTTP 协议中一般会携带两个带有来源信息的字段:

Origin

指示了请求来自于哪个站点。该字段仅指示服务器名称,并不包含任何路径信息, 用于 CORS 请求或者 POST 请求。Origin 在以下两种情况下并不存在:

  • IE 11 不会在跨站 CORS 请求上添加 Origin 标头
  • 302 重定向之后 Origin 不包含在重定向的请求中,因为 Origin 可能会被认为是其他来源的敏感信息。对于 302 重定向的情况来说都是定向到新的服务器上的 URL,因此浏览器不想将 Origin 泄漏到新的服务器上。

Referer

包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。

  • 对于 Ajax 请求,图片和 script 等资源请求,Referer 为发起请求的页面地址。
  • 对于页面跳转,Referer 为打开页面历史记录的前一个页面地址

在以下情况下,Referer 不会被发送:

  • 来源页面采用的协议为表示本地文件的 “file” 或者 “data” URI
  • 当前请求页面采用的是非安全协议,而来源页面采用的是安全协议(HTTPS)

虽然 HTTP 有明确要求, 也有 Referrer Policy 草案对浏览器如何发送做了详细规定, 但是浏览器实现可能有差别, 不能保障安全性. 低版本浏览器,Flash 等情况可能丢失或不可信, 新的 Referrer 规定了五种策略:

States 作用
no-Referrer 任何情况下都不发送 Referrer 信息
no-Referrer-when-downgrade 仅当协议降级(如 HTTPS 页面引入 HTTP 资源)时不发送 Referrer 信息。是大部分浏览器默认策略
origin 发送只包含 host 部分的 referrer.
origin-when-cross-origin 仅在发生跨域访问时发送只包含 host 的 Referer,同域下还是完整的。与 Origin Only 的区别是多判断了是否 Cross-origin。协议、域名和端口都一致,浏览器才认为是同域
unsafe-url 全部都发送 Referrer 信息。最宽松最不安全的策略

设置 Referrer Policy 的方法有:

  • 在 HTTP 的 CSP(Content Security Policy)设置

    Content-Security-Policy: referrer no-referrer|no-referrer-when-downgrade|origin|origin-when-cross-origin|unsafe-url;
  • 页面头部增加 meta 标签, 默认 no-referer 策略

    <meta name="referrer" content="no-referrer|no-referrer-when-downgrade|origin|origin-when-crossorigin|unsafe-url">
  • a 标签增加 Referrer Policy 属性, 只支持三种

    <a href="http://example.com" referrer="no-referrer|origin|unsafe-url">xxx</a>

发起请求的来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。业务上需要针对各种情况作出过滤规则, 一般优先使用 Origin 确认来源信息就够了,Referrer 变数太多比较适合打辅助. 但是如果两者都获取不到的情况下, 建议直接进行阻止.

同源规则能简单防范大多数 CSRF 攻击, 配合关键接口做额外处理能更好提高安全性.

SameSite

一种新的防止跨站点请求伪造(cross site request forgery)的 http 安全特性。该值可以设置为 StrictLax, 现阶段只有部分主流浏览器支持, 仅做了解即可

Set-Cookie: key=value; SameSite=Strict/Lax
  • Strict: 跨域请求或者新标签重新打开都不会携带该 Cokies
  • Lax: 这个请求是(改变了当前页面或者打开了新页面)且同时是个 GET 请求,则携带。

还有一个比较严重的问题是 SameSite 不支持子域名.

附加验证

验证码

通过图形验证码或者手机验证码或者邮箱验证等多种方式强制用户进行交互可以有效遏制 CSRF 攻击, 缺点是步骤比较繁琐, 只适用于如涉及金额, 密码相关等关键请求,

CSRF Token

基于攻击者无法获得用户信息的特性, 我们可以在前后端交互中携带一个有效验证“令牌”来防范 CSRF 攻击, 大概流程:

  1. 当用户首次登录成功之后, 服务端会生成一个唯一性和随机性的 token 值保存在服务器的 Session 或者其他缓存系统中,再将这个 token 值返回给浏览器;
  2. 浏览器拿到 token 值之后本地保存;
  3. 当浏览器再次发送网络请求的时候, 就会将这个 token 值附带到参数中 (或者通过 Header 头) 发送给服务端;
  4. 服务端接收到浏览器的请求之后, 会取出 token 值与保存在服务器的 Session 的 token 值做对比验证其正确性和有效期。

在大型网站一般使用多台服务器, 用户请求经过负载均衡器路由到具体的服务器上, 如果使用 Session 默认储存在单机服务器内存中, 在分布式环境下同一用户的多次请求可能会指向不同的服务器上, 而其他的服务器无法共享 Session 导致 Session 机制失效无法验证, 所以分布式集群中 Token 需要储存在 Redis 等公共储存空间.

因为读取和验证 Token 会有复杂度和性能的问题, 还有种方式采用 Encrypted Token Pattern 方式, 通常是使用 UserID、时间戳和随机数,通过加密的方法生成而非随机性, 之后请求校验不需要读取而是直接计算即可, 这样既可以保证分布式服务的 Token 一致,又能保证 Token 不容易被破解。

双重 Cookie 验证

相较于 CSRF Token, 这种方式比较简单实现但是安全性较低. 大概流程:

  1. 用户访问页面之后域名被注入随机字符串 Cookie
  2. 浏览器发起请求时会取出该 Cookie 字符串添加到 URL 参数中
  3. 服务端验证是否一致

没有大规模应用除了安全性问题还有一个就是跨域可能导致获取不到 Cookie.

  1. 用户访问网站域名www.test.com, 服务端 api 域名api.test.com,
  2. 如果想要共用 Cookie 就必须注入到test.com, 然后子域名都能获取到
  3. 同理每个子域名都能修改该 Cookie, 如果某个子域名被攻击了
  4. 攻击者可以自己配置一个 Cookie 破解双重 Cookie 验证机制拦截
退出移动版