本文描述了各种类型的安全攻击以及减轻这些攻击。
点击劫持(Clickjacking)
表现
“点击劫持”攻击允许恶意页面 以用户的名义 点击“受害网站”。
实现
- 用户登录微博后,
weibo.com
的cookies
里包含登录信息。 - 用户访问恶意页面(可能是点击了广告或者其他方式);
- 恶意页面中嵌入了一个透明的
<iframe>
,其src
来自于weibo.com
; - 页面勾引用户点击了一个
<button>
,实际上用户点击的是覆盖来自<iframe>
中的微博的点赞按钮👍;
防御
使用 Samesite cookie
特性
具有 SameSite
特性的cookie
仅在网站是通过直接方式打开(而不是通过 iframe 或其他方式)的情况下才发送到网站。如果网站在 cookie
中具有 samesite
特性,像这样:Set-Cookie: authorization=secret; sameSite=Lax
,当在另一个网站中的 iframe
中打开微博时,此类 cookie
将不会被发送,微博登录失败,攻击失败。
XSS(Cross-Site Scripts)
跨站脚本攻击:因为 CSS 容易和样式表缩写混淆,所以简称 XSS;
表现
攻击者将恶意 JS 代码注入网站。
来源
- 来自用户生成的信息,后端未做清洗,直接进行拼接进 HTML 中,返回给客户端;
- 来自URL:前端或者后端从
url query
、url hash
中直接读取参数拼接进 HTML 中。
分类
XSS 攻击分为三类:存储(也称为持久)、反射(也称为非持久)和基于 DOM。
-
存储型 XSS 攻击
- 攻击代码来源于用户输入,比如评论、留言功能。
- 注入的脚本永久存储在目标服务器上。然后,当浏览器发送数据请求时,受害者会从服务器检索此恶意脚本。
-
反射型 XSS 攻击
- 攻击代码来源于 URL
- 用户打开带有恶意代码的 URL 时,服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
-
基于 DOM 的 XSS 攻击
- 攻击代码来源于 URL
- 用户打开带有恶意代码的 URL 时,前端 JavaScript 取出 URL 中的恶意代码并执行。
- 获取和执行恶意代码都由浏览器完成,属于前端 JS 代码自身的安全漏洞。
防御
- 改成纯前端渲染,把代码和数据分隔开;
- 对 HTML 做充分转义;
- 不要把不可信的数据拼接到字符串中传递给 JS 执行;
- 使用
CSP
安全策略; - 使用
Samesite Cookies
、HttpOnly
亡羊补牢,即使被注入恶意代码,攻击者也拿不到Cookie
;
CSRF(Cross-Site Request Forgery)
表现
- 用户登录银行网站
bank.com
,bank.com
下的cookies
存储了用户登录token
; - 用户访问了恶意网站
evil.com
,evil.com
在网页中设置了表单 evil.com
向bank.com
发送了一个请求:bank.com?payfor=22000&to=xoyimi
。浏览器会默认携带bank.com
的cookies
。bank.com
接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。bank.com
以受害者的名义执行了payfor=22000&to=xoyimi
。
途径
通过图片URL、超链接、CORS、Form提交等等一切可以发送请求的地方。
防御
- 验证 HTTP 的
Referer
和Origin
请求头; - 在 HTTP 头中自定义属性并验证或者使用
CSRF-Token
技术(因为第三方网站只会发送 Cookie,不会发送自定义 header); - 在
Cookie
中设置sameSite=[Lax|Strict]
,Chrome 51 及以上可以开启此功能; - 不在 GET 请求中做增删改操作,
sameSite=Lax
表单 GET 请求默认会发送Cookie
;
tips: 方案1,2要防止黑客配合
XSS
攻击获取同源脚本执行权限;
这里着重介绍一下 SameSite Cookies
Chrome 80 之前默认为SameSite=None
。
Chrome 80 及以上默认开启 SameSite=Lax
。
如单独写入SameSite
则等同于SameSite=Strict
。
值得注意的是,火狐 和 Safari 并没有默认开启 SameSite=Lax
,仍需要手动设置。
跨站请求类型 | 示例 | None | Lax | Strict |
---|---|---|---|---|
页面跳转 | <a href="..."></a> | 发送 Cookie | 发送 Cookie | 不发送 |
页面跳转 | window.open() | 发送 Cookie | 发送 Cookie | 不发送 |
页面跳转 | 修改 location | 发送 Cookie | 发送 Cookie | 不发送 |
页面跳转 | <form method="GET" action="..."> | 发送 Cookie | 发送 Cookie | 不发送 |
页面跳转 | <form method="POST" action="..."> | 发送 Cookie | 不发送 | 不发送 |
预加载(预跳转) | <link rel="prerender" href="..."/> | 发送 Cookie | 发送 Cookie | 不发送 |
页内跨站请求 | <iframe src="..."> | 发送 Cookie | 不发送 | 不发送 |
页内跨站请求 | <img src="..."> | 发送 Cookie | 不发送 | 不发送 |
页内跨站请求 | ajax (withCredentials:true) | 发送 Cookie | 不发送 | 不发送 |
页内跨站请求 | fetch (credentials:include) | 发送 Cookie | 不发送 | 不发送 |
值得注意的一点是:即便
ajax/fetch
声明携带credentials
,但在SameSite=[Lax/Strict]
下也不会发送Cookie
.
和 CSRF 嗦拜拜~
以上皆在跨站的情况下。