小议防XSS跨站脚本攻击

闲话

本来我以为“防XSS跨站脚本攻击”离我这个渣渣还有很远的距离,没想到做“表白墙”这个项目时具体接触了。在此感谢EDA实验室的大神学长给予的教导。

正题

本来ejs模板引擎自带XSS过滤功能,字符串输出时默认是经过escape转义编码的:

// 用=号输出,就会被escapge转义编码 
<%= VARIABLE_NAME %>

表白墙的后端是原本的微博改的,微博里保存推文的Post对象是这样写的:

function Post(username, post, time) {
    this.user = username;
    this.post = post;
   ………………
}

然而表白贴的提交表单要包含 被告白方,正文,告白方 三个内容,咋存呢?
按照常人的思路肯定是改Post对象:

function Post(username, to, from, post, time) {
    this.user = username;
    this.post = post;
   ………………
}

然而本天(dou)才(bi)不想(gan)改后端,想用前端来生成告白贴的格式。于是在后端只改了:

exports.post = function(req, res) {
…………
var post = new Post(currentUser.name,
"To:#" + req.body.lovername + '#' + req.body.post + "#From:#" + req.body.yourname);
…………

为啥要加“#”呢?本天(dou)才(bi)写了这样的script生成告白贴的格式:

//使用字符串切割函数分出from,post,to,再拼接字符串
newtext = posttext[i].textContent.split('#');
    if( newtext[4].indexOf("匿名") > -1 ) {
            …………
    }else{
    //强行拼接出告白贴
         posttext[i].innerHTML = "<h3>" + "TO:" +                     newtext[1] + "</h3><p>" + newtext[2] + "</p><h3             style='text-align: right'>" + "FROM:"  + newtext[4]             + "</h3>";}

看起来很有趣,是吧?
然而这样相当于把ejs已经转义并输出的的东西又通过不安全的方式再输出一次。然后学长就用了10秒钟给我演示了一次XSS攻击。
耻辱啊。
不过学长输入的代码虽然留在了网页上,但没有运行,为何呢?这个问题值得探讨。先留一个坑。

目前我暂时在上文代码里加了两行代码:

posttext[i].textContent = posttext[i].textContent.replace(/\</g, "&lt");
        posttext[i].textContent = posttext[i].textContent.replace(/\>/g, "&gt");

也就是把尖括号转义了。但这样就一劳永逸了吗?并不!
这篇博文里介绍了绕过防御措施的方法
用八进制/十六进制字符串绕过检测,这方法太牛了……

在实际投入使用的网站中,会有黑名单字段检测系统来保障用户输入的东西没有问题(ejs的转义应该也是类似这样的东西)。但真正能保证安全的还是改后端。
后端我也不是不会改,只是每次改完都要清一下数据库再运行不然会莫名其妙地报错,为了偷一个懒惹来了这么一大串麻烦,这才是真正的教训啊…………以后做项目绝对不能有投机取巧的心理。