如果想用script做自己的网名?

    思考这样一个题目,有一个人他想注册一个网名叫做<script>alert(1);</script>。怎么样实现呢?一般情况下,我们在注册登陆或者评论或与用户有数据交互的时候,都是会把这些给过滤掉的,当我们不允许用户这么做的时候,是为了可以防止部分xss。但如果网站允许这么叫,那怎么实现呢?也就是说我们既要输出相应的标签,又要防止xss。

    Web是一个集众多不同环境同时开发的平台,我们经常要夸平台传输数据。比如客户端与服务器的通信就是在客户端和服务器这两个不同的环境上传输数据。如果我们传输的数据没经过过滤就有可能在各种环节被注入攻击,所以在传输过程中的字符转义是很重要的。


    先看看怎么解决这个问题

    方法一-自定义转义后输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="box2">
    <pre>
    <a href="">baidu</a>
    </pre>
    <pre>
    &nbsp;&lt;a&gt;alert('1');</a>
    afdfs
    adsfas fdsfafdasf
    dafdsS
    </pre>
    &nbsp;&lt;a&gt;alert('1');</a>
    afdfs
    ttttt
    </div>

    大家猜猜上面这会输出一种什么样的效果:
    zyzf
    正常情况下,我们所有的html标签如果直接输出的话,是不可能显示出来的,那些标签都是经过浏览器解析然后显示到页面上的,所以我们要通过转义这样一种方法来实现。

    也就是说比如小明要注册一个网站,他注册的是<script>alert(1);</script>这样一个名字,我们可以不用对script标签进行完全过滤,我们只需要进行一下转义,把里面的<>这种全部转义成实体。并存到数据库,当我们要显示小明的网名的时候,我们输出到浏览器的不是script标签,而是被转义了以后的,这样也就不会造成安全问题。

    但是这样也可能存的问题:【数据传输最普遍的形式就是字符串传输,这也是最容易被注入的。要保证在解析时候不被注入,字符的转义就很有必要!做字符转义除了可以避开注入外还可以解决双字节字符的编码问题。】但是转义之后数据的量会变大,所以在传输的带宽方面会有一定的开销

    方法二-方法优化利用白名单

    刚刚我们提到了,如果我们把所有的都转义后数据量会变大,所以很占据带宽,所以虑数字、字母,空格,这些可能会被大量使用,因此把他们加入白名单,在白名单之外的字符一律转义。想想为啥叫做白名单,黑名单与之对应。


    再看看一般我们是怎么转义过滤的

    删除所有的HTML TAG

    1
    2
    3
    4
    function rmHtmlTab(tab) {
    return tab.replace(/<\/?[^>]*>/g,'');//删除所有HTML标签
    }
    console.log(rmHtmlTab("<script>alert('1');</script>"));

    输出:alert('1');,稍微解释下这个正则的意思:匹配<>或者</>,中间啥都可以,但是不能有>。这样就可以把所有的这样的标签全部过滤掉,只输出文本。


    不删除所有的TAG,全部转义

    1
    2
    3
    4
    5
    6
    7
    function escape(str){
    return str.replace(/[<>&"]/g,function(c){
    var obj = {'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'};
    return obj[c];
    });
    }
    console.log(escape("<a>alert('1');</a>"));

    lt;a&gt;alert('1');&lt;/a&gt;最后转义成了你想要的样子。如果是要将转义符换成普通字符,就把obj里的属性和值差不多换一下就行啦。如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function entityEscape(str){
    var obj = {'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'};
    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){
    console.log(all);
    return obj[t];
    });
    }
    console.log(entityEscape("&nbsp;&lt;a&gt;alert('1');</a>"));

    最后结果为:
    转义

    这里顺便总结下replace的回调函数:当replace方法执行的时候每次都会调用该函数,返回值作为替换的新值。

    1. 第一个参数为每次匹配的全文本($&)。
    2. 中间参数为子表达式匹配字符串,个数不限.( $i (i:1-99))
    3. 倒数第二个参数为匹配文本字符串的匹配下标位置。
    4. 最后一个参数表示字符串本身。
    
    1
    2
    3
    4
    5
    6
    7
    function entityEscape(str){
    var obj = {'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'};
    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(a,b,d,c){
    console.log(a+" "+d+" "+b+" "+c);
    //return obj[c];
    });
    }

    如上就会输出这样的格式:&nbsp; 0 nbsp &nbsp;&lt;a&gt;alert('1');</a>


    再看个小例子

    这个例子跟我们的问题关系其实并不是很大,只是我在做上面的总结时,发现了下列问题:

    1
    2
    3
    4
    <div id="box">
    </div>
    <div id="box1">
    </div>

    大家想想下面两种不同的情况会输出什么呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //情形一
    var box = document.getElementById("box1");
    box.innerHTML = "<script>alert('a');</script>";
    //情形二
    var box = document.getElementById("box1");
    box.innerHTML = "&lt;script&gt;alert('a');&lt/script&gt";
    //情形三
    var script = document.createElement('script');
    var text = document.createTextNode('alert(1)');
    script.appendChild(text);
    var box1 = document.getElementById('box1');
    box1.appendChild(script);
    //情形四
    console.log(escape("&nbsp;&lt;a&gt;alert('1');</a>"));

    答案是第一种情况不会输出alert(a),第二种情况会输出alert(a),第三种情况会输出alert(1)。想想这是为啥呢?

    总结

    以前虽然知道转义这种东西,但是实际项目里用过的,都是直接把所有的标签给过滤了,也就没有实际操作过,涨知识了。