Cross Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使其在用户的浏览器上运行。通过这些恶意的脚本,攻击者可获取用户的敏感信息如:Cookie、Session等。
Cross Site Scripting 的英文首字母缩写应为 CSS,但是因为其和我们的样式语法 CSS(Cascading Style Sheets)一样,所以将 CSS 改成 XSS。
类型主要包括:
- 反射型 XSS
- 存储型 XSS
- DOM 型 XSS
反射型 XSS
- 攻击者构造出特殊的 URL,其中包含恶意代码。
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,从而窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站的数据接口。
常见于通过 URL 传递参数的功能,如网站搜索、跳转等
场景
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反射型 XSS</title>
</head>
<body>
<form action="/" method="GET">
<div>
<label for="name">输入内容</label>
<input type="text" id="name" name="keyword">
</div>
<button>搜索</button>
</form>
{{if keyword }}
<div>搜索内容:{{@ keyword }}</div>
<ul>
<li>搜索结果xxx</li>
<li>搜索结果xxx</li>
<li>搜索结果xxx</li>
</ul>
{{/if}}
</body>
</html>
服务端
const express = require('express')
const app = express()
app.engine('html', require('express-art-template'))
app.get('/', (req, res) => {
res.render('xss.html', {
keyword: req.query.keyword
})
})
app.listen(3000, () => {
console.log('服务器启动成功:http://localhost:3000')
})
攻击过程:前端 -> 后端 -> 前端
存储型 XSS
- 攻击者将恶意代码提交到目标网站的数据库中
- 攻击者打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
- 用户浏览器接收到响应后解析执行,从而窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站的数据接口。
- 存储型 XSS 和 反射型 XSS 的主要区别:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL里
常见于评论、文章详情等地方。攻击者将包含恶意代码的文章数据发布,存储到数据库中。其他用户访问时就会触发该恶意代码
场景
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>存储型 XSS</title>
</head>
<body>
<h1>文章/评论内容</h1>
{{if title }}
<div>{{ title }}</div>
<div>{{@ content }}</div>
{{/if}}
</body>
</html>
服务端
const express = require('express')
const app = express()
app.engine('html', require('express-art-template'))
// // 反射型
// app.get('/', (req, res) => {
// res.render('xss.html', {
// keyword: req.query.keyword
// })
// })
// 模拟数据库
const db = [
{
id: 1,
title: '标题1',
content: '内容1'
},
{
id: 2,
title: '标题2',
content: '内容1 <script>alert("xss")</script>'
}
]
// 存储型
app.get('/:id', (req, res) => {
const data = db[req.params.id] || {}
res.render('xss2.html', data)
})
app.listen(3000, () => {
console.log('服务器启动成功:http://localhost:3000')
})
攻击过程:前端 -> 后端 -> 数据库 -> 后端 -> 前端
攻击者发布了 A 文章,用户访问了该文章
DOM 的 XSS
- 攻击者构造出特殊的 URL,其中包含恶意代码
- 用户打开带有恶意代码的 URL
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
- 从而窃取用户信息获取冒充用户行为
DOM 型 XSS 跟前两种 XSS 的区别: DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器执行完成,属于前端自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。
场景
HTML 属性
<img src="xxx" onerror="alert('xss')" />
<body onload="alert('xss')"></body>
<a href="javascript:alert('xss')"></a>
XSS 防御
通过上述的 XSS 步骤我们知道 XSS 攻击主要是通过两大要素:
- 攻击者提交恶意代码
- 浏览器执行恶意代码
输入过滤:
- 由前端过滤,提交后端。如果攻击者绕过前端过滤,直接构造请求一样可以将恶意代码提交
- 如果由后端在写入数据库前过滤,如
10 < 20
,在入库是转义就会变成10 < 20
。此时输出给浏览器是可以直接识别的,但是如果输出给其他客户端就会出现乱码的情况。
结论:不建议在输入的时候对数据进行转义,而是在输出的时候
防御 反射型 XSS 和 存储型 XSS
常见方法:
- 改成纯前端渲染,把代码和数据分隔开。
- 对 HTML 做充分转义。
纯前端渲染
- 通过静态 HTML,加载 JavaScript 脚本
- 通过 JavaScript 脚本请求 Ajax,来加载业务数据
- 调用 DOM API 将数据更新到页面中
通过 DOM API 如:设置内容文本(.innerText),设置属性(.setAttribute),设置样式(.style)等。纯前端需要注意避免 DOM 型 XSS 攻击(例如 onload 事件、href 中的 javascript:xx 等)
转义 HTML
如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分得转义。
如:把 & < > " ' / 等字符进行转义,但是这不够完善。所有我们需要借助转义库,来进行完善转义。
例如 Node.js 中常用的转义库 js-xss
预防 DOM XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
使用 .innerTHML
、.outerHTML
、document.write()
须谨慎,尽量使用 .textContent
、.setAttribute
等
CSP
CSP (Content-Security-Policy) 的简称。是一个额外的安全层,用于检测和削弱某些特定的攻击。