什么是XSS攻击?
举一个例子:页面中有一个input框,我们需要把用户输入的内容展示在页面中,具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
input {
width: 200px;
height: 30px;
border-radius: 4px;
border: 1px solid gray;
}
</style>
</head>
<body>
<input id='btn' type="text">
<button id="sub" type="submit">提交</button>
<div id='content'></div>
</body>
<script>
var inp = document.getElementById('btn');
var sub = document.getElementById('sub');
var content = document.getElementById('content')
sub.addEventListener('click', function() {
content.innerHTML = inp.value
})
</script>
<script src="underScore.js"></script>
<script>
</script>
</html>
如果用户输入的是脚本呢?
<a href="javascript:alert(1)">攻击</a>
点击攻击,页面会出现一个弹框
这里加入的js代码可以顺利的执行,这样就很危险了,看下面的代码:
<img src=@ onerror='var s=document.createElement("script");s.src="xss.js";document.body.appendChild(s);' />
一份意外的js文件被引入,试想,如果这里是个绝对路径,里面是提前编辑好的js代码,这样就可以在你的页面上为所欲为,包裹回去cookie,用你的身份登陆网页,获取用户信息等等。总之,就是很危险。
为了防止这种情况的发生,我们可以将网址上的值取到后,进行一个特殊处理,再赋值给 DOM 的 innerHTML。
underScore源码中,把一些不安全的字符都转变成了字符串实体。
什么是字符串逃逸?
就是把一些不安全的字符都转变成了字符串实体。
下面列举了一些字符串对应的字符串实体:
// List of HTML entities for escaping.
var escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
当用户输入的字符串中包含这里的key是,就把它转换为对象的value。这样说大家可能不太明白是在干什么?为什么这样转换就能防止XSS攻击了,别急,往下看:
把刚刚的代码输入替换一下:
<a href="javascript:alert(1)">攻击</a>
把尖括号替换成字符串实体后,看页面会输出什么?页面输出了输入框的内容,但不会把输入字符串当成标签输出,是以字符串的形式输出的,这样就无法再注入js代码了,因为到页面都会被转换为字符串,这样就安全多了。
underScore内部的escape函数就做了这样的事情:
// Functions for escaping and unescaping strings to/from HTML interpolation.
function createEscaper(map) {
var escaper = function(match) {
return map[match];
};
// Regexes for identifying a key that needs to be escaped.
var source = '(?:' + keys(map).join('|') + ')';
var testRegexp = RegExp(source);
var replaceRegexp = RegExp(source, 'g');
return function(string) {
string = string == null ? '' : '' + string;
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
};
}
var escape = createEscaper(escapeMap);
testRegexp正则写成字面量就是这样的:
var str = '<a href="javascript:alert(1)">攻击</a>'
var testRegexp = /(?:&|<|>|"|'|`)/;
console.log(testRegexp.test(str)) // true
var str = '防止攻击`设计`'
var testRegexp = /(?:&|<|>|"|'|`)/;
console.log(testRegexp.test(str)) // true
这里(?:pattern)是非捕获分组,不加这个也可以实现功能,但是会影响性能,推荐一本书《JavaScript正则表达式迷你书》
所以这里加了非捕获分组,不去捕获匹配的内容。
正则如果不加全局匹配,只会匹配到一个后停止:
var str = '防止攻击`设计`'
var testRegexp = /(?:&|<|>|"|'|`)/;
str.replace(testRegexp, function(match) {
console.log(match) // `
})
这里就匹配到了第一个'`',所以需要给这个正则加全局匹配,至于为什么不直接使用使用全局匹配,估计也是考虑性能的问题,全局匹配肯定要比非全局匹配消耗性能。
var str = '防止攻击`设计`'
var testRegexp = /(?:&|<|>|"|'|`)/g;
str.replace(testRegexp, function(match) {
console.log(match) // `
})
换成全局的,就会打印两次:
最后的使用,是需要调用这个函数,处理用户输入的字符串就可以有效的防止XSS攻击了。
content.innerHTML = _.escape(inp.value)