# 什么是 XSS
Cross-Site Scripting(跨站脚本攻击)简称 XSS。本质是将恶意代码 / 脚本注入目标网站,从而读取用户的敏感信息(如 cookie,sessionId)危害数据安全。
# XSS 常见场景
例如,我们有时候需要从 URL 取参数在页面上展示。
1 | <body> |
如果这时访问的 URL 查询参数为: keyword=<img src="x" onerror="alert(1)">
。
网站会尝试从一个不存在的地方下载 img,然后触发 onerror
之后调用 alert(1)
,如果换成读取用户的 cookie 之类敏感消息,就会造成危险。
# XSS 分类
# 存储型 XSS
- 攻击者把恶意代码提交到目标网站的数据库。
- 服务端把恶意代码从数据库取出返回给客户端(如拼接 URL 参数)。
- 客户端解析代码中的恶意代码并执行。
- 恶意代码可能将用户的敏感信息取出并发送到自己的网站。
# 反射型 XSS
- 攻击者构造出一个恶意 URL 诱导用户点击。
- 打开 URL 后,服务器取出恶意代码并返回给客户端。
- 客户端解析代码中的恶意代码并执行。
- 恶意代码可能将用户的敏感信息取出并发送到自己的网站。
存储型 XSS 和反射型 XSS 的区别在于:前者是将恶意代码注入数据库,后者是存储在 URL 中。
# DOM 型 XSS
- 攻击者构造出一个恶意 URL 诱导用户点击。
- 打开 URL 后,前端 JS 读取 URL 参数中的恶意代码并执行。
- 恶意代码可能将用户的敏感信息取出并发送到自己的网站。
DOM 型 XSS 和前两者的区别在于:其被攻击的过程由浏览器(前端 JS)完成,不涉及服务端。
# XSS 预防
我们可以从 XSS 攻击的两个要素来预防。
- 攻击者提交恶意代码。
- 浏览器执行恶意代码。
# 输入过滤 / 转义
比如前端对用户的输入内容进行 HTML 转义后再发送给服务端,是否可行?
答案是不可行。攻击者可以构造请求做到跳过前端的过滤。
那么换个角度,我们在服务端进行代码的转义,然后把安全的代码存储到数据库,之后做到将安全的代码返回给前端,是否可行?
这种做法有两种潜在的问题:
- 转义后的代码有可能被输出到浏览器前端 / JS 客户端执行。浏览器会对转义字符进行编码,而 JS 不会。
- 转义的代码如果拼接在 HTML 中可以编码后正常运行,但是如果返回后赋值给 JS 变量也无法编码。
# 小心的拼接 HTML
如果必须要有拼接 HTML 的操作,可以使用合适的转义库。或尽可能使用 .innerText
或 .setAttribute()
之类的 API 进行赋值拼接,减少使用 .innerHTML
此类操作。
# Content Security Policy(CSP)
有时候注入的恶意脚本是某个外部 src 的 script 脚本,这时候我们可以配置 CSP 白名单来阻止未知来源的的 script 资源加载。
你可以在 HTTP 头或是 meta 标签进行配置。这里简单介绍一下 HTTP 头如何配置。
1 | Content-Security-Policy: default-src 'self' |
上面代码表示所有的资源都只能从当前域名加载。
还有常见的场景是对 script 脚本进行单独配置,例如 nonce 值来发送一个 token,当加载脚本时进行验证。
1 | Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa' |
# 其他安全措施
- HTTP-only:对 cookie 设置 HTTP-only 属性,防止在 XSS 攻击后直接读取 cookie。
- 验证码:在进行操作时需要读取屏幕的验证码输入验证,辨别人类与机器。
参考文章:
美团 - 前端安全系列(一):如何防止 XSS 攻击?
阮一峰 - Content Security Policy 入门教程