引言
当代社会网络的快速普及,人们越来越依赖网络,广大网络用户通过互联网了解更多信息的同时,也面临着信息泄露的风险。网络安全成为了社会发展的焦点话题,展现在用户面前的前端界面越来越多元化,这就需要更多地去了解信息安全,这篇文章主要谈xss攻击 github
xss(Cross Site Scripting)跨站脚本攻击
观察以下代码,它们如果作为html的节点内容,就会引起xss攻击
<p><svg onload=prompt(/xss/)></p>
<iframe src="http://www.baidu.com" height="250" width="300"></iframe>
<a href="javascript:alert('xss')">你好</a>
<img src=1 onerror=alert('xss')>
因此,XSS 攻击,一般是指攻击者通过在网页中注入恶意脚本,当用户浏览网页时,恶意脚本执行,控制用户浏览器行为的一种攻击方式。
然后谈谈跨站这个词: 我们一般希望网站运行的所有逻辑均来自本站,当本站运行了其他网站的东西,例如常见的js脚本,就可能产生跨站脚本攻击,当然在这里我们可以理解为不局限于远程脚本,因为通常探测是否页面是否存在潜在的xss攻击,都会使用一个alert框,也被称做xss探测器。举个简单的例子:
例子1
// server.js
const express = require('express');
const app = express();
app.get('/', function(req, res){
res.send('hello world')
})
app.get('/index', function(req, res){
const id = req.query.id
res.send(`<textarea>${id}</textarea>`)
})
app.listen(3001)
在浏览器输入网址 localhost:3001/index?id=</textarea><script>alert(1)</script>
在谷歌浏览器的运行结果
按下回车的那一瞬,并没看到预期的弹框,内心一阵失落,但同时也注意到了textarea输入框并没有东西,于是想莫非是谷歌浏览器已经做了什么事情,来预防此种简易的攻击,打开console面板
果然,好吧,输给了chrome,我还是心服口服的,在这里值得提一下,谷歌的安全机制还是很完善的,这种简单的反射性跨站攻击,谷歌直接就帮忙处理拦截了,safari也会对这种简易的攻击做基本的拦截。
换个浏览器,firefox
好吧,好大一个框!
xss攻击的原理
本质: 插值造成的注入。
脚本程序 + 数据源 = 渲染结果
脚本程序: <textarea>${id}</textarea>
数据源 | 渲染结果 |
---|---|
id: 1 | <textarea>1</textarea> |
id: </textarea><script>alert(1)</script> |
<textarea></textarea><script>alert(1)</script></textarea> |
在这个例子中,攻击者首先把盛放数据的容器提前闭合,然后通过script标签为数据赋予行为,让数据源不再单纯,改变整个程序的逻辑,从而实现攻击的目的
只是弹个框吗?
说到这里,我们已经用了个简单的例子引出了今天的主题xss,也许有人心里会有疑问,不就是弹个框吗?果真如此吗?我们可以换个角度考虑一下,我们平时写的脚本能干什么?
-
获取页面数据
-
获取cookie
-
劫持前端的逻辑
-
发送请求
-
...
因此,xss也可以干这些事,只有你想不到,没有黑客做不到的,攻击者通过注入脚本,可以通过dom抓取页面中的数据,也可以通过document.cookie获取用户的cookie,一般cookie保存有用户登陆态,攻击者只需要将这个cookie种在自己的浏览器中,就可以获取用户的各种信息,模拟用户进行各种操作等。
业界被攻击案例
上网搜了下之前被xss恶意攻击的案例,发现了一个典型,作为前端工程师,或多或少都知道站酷这个网站,这是国内非常有名的设计师交流平台,在官网首页有个搜索功能,当输入搜索关键词后点击搜索,关键词会在搜索结果页二次显示 站酷
值得说明的是:现在站酷已经修复了,目前的效果是对标签进行了处理
xss攻击分类
- 反射型xss攻击
- 存储型xss攻击
- dom型xss攻击
我理解的三者的区别:
反射型xss攻击:
通过url链接点击触发,是一次性行为,实质是服务器端没有对用户的恶意输入做安全处理,直接反射相应内容。文章开始的例子1就是一个典型的反射型xss攻击,这类攻击可以通过url辨别,谷歌这类安全性高的浏览器,基本可以自动处理这类攻击
存储型xss攻击:
顾名思义,存储,一般涉及后端数据存储,常见的场景就是APP的意见反馈模块,前端通过接口把用户输入的信息传给后端,当有些后台管理系统需要展示反馈意见时,就会取数据库中的这些数据,当这些数据中含有攻击的脚本,那就造成了存储型xss攻击。这种攻击是持久性的。
dom型攻击:
客户端的脚本程序可以动态地检查和修改页面内容,而不依赖于服务器端的数据。可能引起dom型xss的:使用innerHTML, documen.write属性...
xss的注入点
- html节点内容动态生成
- html属性,属性是由用户输入的信息组成的
<img src="null" onerror='alert(document.cookie)' />
- js代码-js代码存在后台注入的变量或者用户的信息
// test.html
<script>
const info = '';alert(1);''
const data = `hello${info}`
</script>
- 富文本标签
xss防御
1 浏览器自带的防御:
X-XSS-Protection: Chrome 和 Safari 的一项功能,可在检测到反射的跨站点脚本(XSS)攻击时阻止页面加载 可选的值:
- X-XSS-Protection: 0
- X-XSS-Protection: 1
- X-XSS-Protection: 1; mode=block 启用 XSS
- X-XSS-Protection: 1; report=reporting-uri
- 0 禁用 XSS 过滤.
- 1 启用 XSS 过滤(通常在浏览器中默认)。如果检测到跨站点脚本攻击,浏览器将清理页面(删除不安全的部分)。
- mode = block 如果检测到攻击,浏览器将阻止页面的呈现,而不是消毒该页面
- report = (仅限 Chromium)启用 XSS
- 筛选。如果检测到跨站点脚本攻击,浏览器将清理页面并报告违规行为。这使用 CSP report-uri指令的功能发送报告。
- 拦截结果: 防御范围: 反射型xss攻击
2. html标签转义
将标签符号'<', '>'全局转义
...
const escapeHtml = (str) => {
str = str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '&quto;')
.replace(/'/g, ''')
.replace(/ /g, ' ')
return str
}
...
app.get('/index', (req, res) => {
const id = req.query.id || ''
res.send(`<textarea>${escapeHtml(id)}</textarea>`)
})
输出:
用户输入什么,显示什么,并且也没造成xss攻击。3.白名单过滤
高度定制要展示的内容,这里我使用了cheerio来将html字符串解析成html实体, cheerio
// <img src="123" onerror="alert(1)"></img>使用cheerio的输出
{ type: 'tag',
name: 'img',
attribs: { src: '123', onerror: 'alert(1)' },
children: [],
next: null,
prev: null,
parent: null,
root:
{ type: 'root',
name: 'root',
attribs: {},
children: [ [Circular] ],
next: null,
prev: null,
parent: null
}
}
// server.js 无关代码已省略
...
const whiteList = {
'img': ['src']
}
const xssFilter = (html) => {
if (!html) return ''
const $ = cheerio.load(html, {
normalizeWhitespace: true,
xmlMode: true
})
$('*').each((index, elem) => {
if (!whiteList[elem.name]) {
$(elem).remove()
return
}
for (var attr in elem.attribs) {
if (whiteList[elem.name].indexOf(attr) === -1) {
$(elem).attr(attr, null)
}
}
})
onsole.log(html, $.html())
return $.html()
}
app.get('/api', (req, res) => {
const cb = (result) => {
result = result.map(item => {
const { comment, name, time } = item
return {
name,
time,
comment: xssFilter(comment)
}
})
res.send({
data: result,
message: 'success',
status: 1
})
}
model.find({}, cb) // 自定义的查询方法
})
...
过滤结果: 因为白名单只配置了:'img': ['src'], 因此img的onerror属性被过滤,其他不在白名单的标签整体被过滤
4.csp(Content Security Policy)
csp是个http的头,实质是用于规定内容是否可执行,因此只要将用户的内容标记为不可执行,就ok了~~~
// server.js
...
app.use((req, res, next) => {
res.set({
'Content-Security-Policy': "default-src 'self'" // 与文档同源
})
next()
})
...
没有攻击成功