XSS
什么是XSS攻击?如何防范XSS攻击? 什么是CSP?
跨站脚本攻击 XSS 是最常见、危害最大的网页安全漏洞。
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意的Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
XSS 简单点来说,就是攻击者想尽一切办法将可以执行的代码嵌入网页中。
XSS 可以分为多各类,但是总体上我认为分为两类:持久型和非持久型
持久型也就是攻击的代码被服务端写进数据库中,这种攻击危害性很大,因为如果网站的访问量很大的话,就会导致大量正常访问页面的用户都受到攻击。
举个例子,对于有输入文本的一些输入框如:反馈功能来说,就得防范持久型XSS攻击,因为我可以在评论中输入一些js代码
这种情况如果前后端没有做好防御的话,这段反馈就会存储到数据库中,这样每个打开该页面的用户都会被攻击到。
非持久型相比于前者危害就小的多了,一般都是通过修改URL参数的方式加入攻击代码,诱导用户访问链接从而进行攻击。
举个例子,如果页面需要从URL中获取某些参数作为内容的话,不经过过滤就会导致攻击代码被执行
<!-- http://www.xxx.com?id=<script>alert(123)</script> -->
<div>{{id}}</div>
但是对于这种攻击方式来说,如果用户使用的是Chorme这类浏览器的话,浏览器就能自动帮助用户防御攻击。但是我们不能因此就不防御攻击了,因为我们不能确定用户都使用了这类浏览器
对于XSS攻击,通常有两种方法可以用来防御。
第一种: 转义字符
首先,对于用户的输入应该是永远不信任的。最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义
function convert(str) {
str = str.replace(/&/g, '&')
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
return str
}
// 通过转义可以将攻击代码 <script>alert(123)</script>
// -> <script>alert(123)</script>
convert('<script>alert(123)</script>')
const xss = require('xss')
let html = xss('<h1 id="title">XSS Demo</h1><script>alert(123)</script>')
// -> <h1 id="title">XSS Demo</h1><script>alert(123)</script>
以上示例使用了js-xss来实现,可以看到在输出中保留了h1标签且过滤了script标签。
但是对于显示富文本来说,显然不能通过上面的办法来转义所有的字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法(CSP),考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单方式。
第二种:CSP
为了防止XSS攻击,要采取很多编程措施,非常麻烦。很多人提出,能不能根本上解决问题,浏览器自动禁止外部注入恶意脚本?
这就是"网页安全政策"(Content Security Policy,缩写 CSP)的来历。本文详细介绍如何使用 CSP 防止 XSS 攻击。
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。我们可以通过这种方式来尽量减少XSS攻击。
通常可以通过两种方式来开启CSP:
1.设置HTTP响应头信息的 Content-Security-Policy 的字段
2.设置标签。
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
CSP语法
每一条策略都是指令与指令值组成,策略与策略之间用分号隔开,这是在HTTP响应头的写法
Content-Security-Policy:指令1 指令值1;指令2 指令值2;指令3 指令值3
这是meta标签写法,指令与指令值写在content里面
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
CSP指令
指令 | 说明 |
---|---|
default-src : | 定义针对 JavaScript 的加载策略。定义针对所有类型指令设置默认值 |
script-src : | 定义针对 JavaScript 的加载策略。 |
style-src : | 定义针对样式表的加载策略。 |
img-src : | 定义针对图片的加载策略。 |
font-src : | 定义针对字体的加载策略。 |
media-src : | 定义针对多媒体的加载策略,例如:音频标签和视频标签。 |
object-src : | 定义针对插件的加载策略,例如:、、。 |
child-src : | 定义针对框架的加载策略,例如: ,。 |
connect-src : | 定义针对 Ajax/WebSocket 等请求的加载策略。不允许的情况下,浏览器会模拟一个状态为400的响应。 |
sandbox : | 定义针对 sandbox 的限制,相当于 的sandbox属性。 |
report-uri : | 告诉浏览器如果请求的资源不被策略允许时,往哪个地址提交日志信息。 |
form-action : | 定义针对提交的 form 到特定来源的加载策略。 |
referrer : | 定义针对 referrer 的加载策略。 |
reflected-xss : | 定义针对 XSS 过滤器使用策略。 |
CSP指令值
指令值 | 说明 |
---|---|
* | 允许加载任何内容 |
‘none‘ | 不允许加载任何内容 |
‘self‘ | 允许加载相同源的内容 |
www.a.com | 允许加载指定域名的资源 |
*.a.com | 允许加载 a.com 任何子域名的资源 |
https://a.com | 允许加载 a.com 的 https 资源 |
https: | 允许加载 https 资源 |
data: | 允许加载 data: 协议,例如:base64编码的图片 |
script-src 的特殊值
指令值 | 说明 |
---|---|
‘unsafe-inline‘ | 允许执行页面内嵌的<script>标签和事件监听函数,例如style属性、onclick、inline js、inline css等 |
‘unsafe-eval‘ | 允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。 |
nonce | 每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行。 |
hash | 列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。 |
nonce值的例子如下,服务器发送网页的时候,告诉浏览器一个随机生成的token。
Content-Security-Policy: script-src 'nonce-adgd897835uiehfg78e6'
页面内嵌js,必须有这个token才能执行。
<script nonce='adgd897835uiehfg78e6'> // js </script>
hash值的例子如下,服务器给出一个允许执行的代码的hash值。
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
下面的代码就会允许执行,因为hash值相符。
<script>alert('Hello, world.');</script>
注意,计算hash值的时候,<script>标签不算在内。
除了script-src选项,nonce值和hash值还可以用在style-src选项,控制页面内嵌的样式表。
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; child-src https:; img-src https:">
Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:
上面代码中,CSP 做了如下配置:
script-src: 'self'; // 只允许加载本站资源
object-src 'none'; // 不信任任何URL,即不加载任何资源
child-src https: // 必须使用HTTPS协议加载
img-src https://* // 只允许加载HTTPS图片
注意:
- script-src和object-src是必设的,除非设置了default-src。因为攻击者只要能注入脚本,其他限制都可以规避。而object-src必设是因为 Flash 里面可以执行外部脚本。
- script-src不能使用unsafe-inline关键字(除非伴随一个nonce值),也不能允许设置data:URL。