# 什么是 XSS 攻击
XSS 全称是跨站脚本攻击(Cross-Site Scripting),为了避免和 CSS 冲突,第一个简写字母改为 X。它是一种代码注入形式的攻击,攻击者通过向网站注入恶意脚本,获取用户敏感信息,进行恶意操作。
攻击者可以通过哪些方式注入恶意脚本呢?
- 用户输入
- URL 参数
- 请求参数
我们先来看几个 XSS 攻击的小例子:
例子一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>请输入您的评价内容:</p>
<input id="input" type="text">
<button onclick="submit()">提交</button>
<script>
function submit() {
document.write(input.value)
}
</script>
</body>
</html>
当我们在输入框输入 <script>alert('xss')</script>,并点击提交按钮后,页面弹出了 alert 弹窗。
用 innerHTML 插入文本到网页中有可能成为网站攻击的媒介,从而产生潜在的安全风险问题。所以HTML 5 中指定不执行由 innerHTML 插入的 <script>标签。

例子二:
下面代码的页面地址为:http://127.0.0.1:5500/index.html?redirect_to=javascript:alert(%27XSS%27)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a id="redirect">跳转。。。</a>
<script>
const href = getUrlParam('redirect_to')
if (href) {
redirect.href = href;
}
function getUrlParam(name) {
const obj = {};
const search = location.search;
if(!search) return '';
const arr = search.split('?')[1].split('&')
arr.forEach(item => {
const res = item.split('=');
obj[res[0]] = res[1];
})
return obj[name]
}
</script>
</body>
</html>
当我们点击 a 标签的时候页面会弹出 XSS 。
案例三:
网页地址 http://127.0.0.1:5500/1.html?goUrl=javascript:alert(%27xss%27)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
location.href = getUrlParam('goUrl')
function getUrlParam(name) {
const obj = {}
const search = location.search.split('?')[1];
if (!search) return '';
search.split('&').forEach(item => {
const arr = item.split('=');
obj[arr[0]] = arr[1] || ''
})
return obj[name]
}
</script>
</body>
</html>
当页面加载完后,会弹出 xss 弹窗。
# XSS 分类
# 存储型
前端将恶意脚本提交到了后端,后端将恶意脚本存储到了数据库,当客户端执行这些脚本后,达到攻击的目的。
存储型 XSS 一般发生在用户提交数据的工具,如:论坛发帖、商品品论、用户私信等。
# 反射型
恶意脚本做为请求参数的一部分发送到服务器,服务器解析参数后,返回给客户端,浏览器执行恶意脚本,达到攻击的目的。
反射型 XSS 漏洞一般发生在通过 URL 传递参数的功能,如搜索、跳转等。
可以看到存储型 XSS 将恶意脚本存储到了数据库,反射型 XSS 将恶意脚本存储到了 URL 中。
# DOM型
# XSS 防范
# 输入过滤
对用户输入的内容进行过滤,需要前后端一起做,因为前端不安全,万一攻击者绕过了前端过滤,恶意数据就被直接提交到了后端。
function htmlEscape(text) {
return text.replace(/[<>"&]/g, function (match, pos, originalText) {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case "&":
return "&";
case "\"":
return """;
}
});
}
即便后端对输入内容进行了过滤也会存在问题。比如 1>2 这个转义后,变成了 1>2,后端把这个转义后的字符串返回给了前端,如果前端直接把这个字符串展示在 html 中,显示是正常的。如果 JS 代码中需要使用这个字符串,那就有问题了。
// 我们期望的字符串是 '1>2',而我们从后端拿到的是 '1>2'
const a = '1>2';
所以不能依赖于对输入过滤,
# CSP
内容安全策略(CSP)用于检测和减轻用于 Web 站点的特定类型的攻击,例如 XSS (en-US) 和数据注入等。
该安全策略的实现基于一个称作 Content-Security-Policy 的 HTTP 首部。可以起到以下作用:
- 禁止加载外域代码,防止复杂的攻击逻辑。
- 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
- 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
- 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
- 合理使用上报可以及时发现 XSS,利于尽快修复问题。
# 限制输入长度
对用户输入限制合理的输入长度,可以增加 XSS 攻击的难度。
# HttpOnly
cookie 设置了 HttpOnly 属性后,通过 JS 就无法访问 cookie 了,可以有效防止 XSS 攻击。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
# XSS 检测
我们可以使用下面这个神奇的代码来手动进行 XSS 漏洞检测,这里简单举个例子,具体使用请参考这篇文章 Unleashing an Ultimate XSS Polyglot (opens new window)
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
# XSS 总结
XSS 防范需要前后端共同努力,不能单靠一方。后端主要方法存储型和反射型的 XSS 攻击,前端主要防范 DOM 型的 XSS 攻击。在对数据进行转义时一定要注意,不要出现乱码。