Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

关于字符转义与注入

  Web是一个集众多不同环境同时开发的平台,我们经常要夸平台传输数据。比如客户端与服务器的通信就是在客户端和服务器这两个不同的环境上传输数据。如果我们传输的数据没经过过滤就有可能在各种环节被注入攻击,所以在传输过程中的字符转义是很重要的。
  数据传输最普遍的形式就是字符串传输,这也是最容易被注入的。要保证在解析时候不被注入,字符的转义就很有必要!做字符转义除了可以避开注入外还可以解决双字节字符的编码问题。但是转义之后数据的量会变大,所以在传输的带宽方面会有一定的开销。
  现在大多数程序都使用的黑名单过滤模式,只过滤掉一些自己觉得有害的字符,比如引号和反斜杠之类的特殊符号。但是道高一尺魔高一丈,即使过滤这些字符还是会有可以注入的地方。比如服务器传给客户端的字符串中如果带SCRIPT的结束标签,直接写在页面上就会造成SCRIPT提前结束,字符串后面的部分变成可以任意操作的HTML。
  把字符串转换成JavaScript字符串最简单的方式是什么?对,用JSON的方法,比如
var str=JSON.stringify("~!@#$%^&*(\"\'\\)<>:");
console.log(eval(str));

  JSON对象的stringify方法是对eval安全的,它转换出来的东西不会被eval注入。但是仅此而已,对于HTML而言它就是不安全的。这个可以用NodeJS来测试,假如有这样的代码 require('http').createServer(function(req,res){
  var data={test:'</script><script>alert(123)</script>'};
  data=JSON.stringify(data);
  res.end('<script>var data='+data+'</script>');
}).listen(8000);

  这个代码发送到客户端后字符串中注入的alert被运行了,因为JSON不对SCRIPT的结束标签转义。
  Web的实际运行环境要比这复杂,不仅是服务器和客户端这样简单的通信,还有服务器对数据库,或者客户端对一些插件的通信。如果我们要为每个环境去考虑不同的字符串安全性,那不仅浪费时间,而且往往容易留下漏洞。
  我们需要一个可以高枕无忧的解决方案,于是考虑了白名单的过滤模式。事实上我一开始甚至想把所有字符都做转义。但考虑数字、字母,空格,这些可能会被大量使用,因此把他们加入白名单,在白名单之外的字符一律转义。这又让我联想到一个JavaScript的函数,escape。这个函数在HTML和JavaScript字符串解析上确实已经足够安全了,它使用的就是白名单模式。但由于它不转义这几个字符“*@-_+./”,所以在其它情况下依然有破绽。观察这些字符会发现基本都是一些通配符,所以它不适合正则表达式的转义或数据库的模糊查询之类的。而且包含了加号甚至不适合用于URL参数转义,因为URL中加号代表空格。
  我觉得字符串中的安全字符只有数字和字母了。不过空格字符的使用频率也很高,而且它也不会造成什么影响,所以把空格也加入白名单。就用这个简单的白名单来实现一个字符转义的功能。 var s="~!@#$%^&*\r\n\t\0次碳酸钴asdfghj123";
var e=superEscape(s);

console.log(new RegExp(e).test(s)); //true

function superEscape(s){
  return s.replace(/[^a-z0-9 ]/ig,function(e){
    var e=e.charCodeAt(0).toString(16);
    if(e.length%2)e="0"+e;
    return (e.length>2?"\\u":"\\x")+e;
  });
};
  这样转义后的字符串甚至可以直接把它直接作为正则表达式的模式串来使用。不过注意这里使用了\x和\u的方式来处理字符,如果是URL可以换成%和%u的形式,如果是写入数据库那就得看具体的数据库支持了。但我觉得,这样的转义才是足够安全的。
  最后,有些人可能会吐槽效率和带宽占用的问题,或者让我直接改用BASE64传输算了。确实BASE64是一个安全的传输解决方案,很多协议中都使用BASE64来传输二进制数据。但是也只有特定的协议才会用,因为这需要双方的协商,而字符串转义只需要单方面的完成就行了。就算法效率而言,我上面的代码写的确实和很渣,但是比起BASE64的效率,字符转义肯定是比较快的。最后的带宽问题也不用纠结,为什么UTF-8会流行开来?UTF-8显然对我国是很不公平的,汉字占3字节,字母才1个字节。如果在意这点带宽就应该用GBK字符集来传输。
网名:
3.80.32.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^