您的位置:首页 > 其它

web安全防范之XSS漏洞攻击

2013-02-18 10:39 916 查看
[align=left]web安全防范之XSS漏洞攻击[/align]
[align=left]最近在cnode社区,由@吴中骅的一篇关于XSS的文章,直接导致了社区的人开始在cnode尝试各种攻击。本文总结了一下这次碰到的一些问题与解决方案。[/align]

[align=left]文件上传漏洞[/align]
[align=left]之前nodeclub在上传图片的时候逻辑是这样的:[/align]
[align=left]1. //用户上传的文件名 [/align]
[align=left]2. var filename = Date.now() + '_' + file.name; [/align]
[align=left]3. //用户文件夹 [/align]
[align=left]4. var userDir = path.join(config.upload_dir, uid); [/align]
[align=left]5. //最终文件保存的路径 [/align]
[align=left]6. var savepath = path.join(userDir, filename); [/align]
[align=left]7. //将用户上传的文件从临时目录移动到最终保存路径 [/align]
[align=left]8. fs.rename(file.path, savepath, callback); [/align]
[align=left]9. [/align]
[align=left]看上去好像没有问题,每个人上传的文件都存放在以用户UID命名的一个文件夹内,并且以当前的时间戳作前缀。但是当有用户恶意构造输入的时候,问题就出现了。当用户上传的文件filename为/../../xxx的时候,上传的文件就会rename到用户文件夹之外,导致用户可以替换现有系统上的任何文件。[/align]
[align=left]这个漏洞相对来说非常的低级,但是后果却是最严重的,直接导致整个系统都可能被用户控制。修复的方法也很简单:[/align]
[align=left]1. var filename = Date.now() + '_' + file.name; [/align]
[align=left]2. [/align]
[align=left]3. var userDir = path.join(config.upload_dir, uid); [/align]
[align=left]4. [/align]
[align=left]5. //获取最终保存到的绝对路径 [/align]
[align=left]6. var savepath = path.resolve(path.join(userDir, filename)); [/align]
[align=left]7. //验证 [/align]
[align=left]8. if (savepath.indexOf(path.resolve(userDir)) !== 0) { [/align]
[align=left]9. return res.send({status: 'forbidden'}); [/align]
[align=left]10.} [/align]
[align=left]11.fs.rename(file.path, savepath, callback); [/align]
[align=left]12. [/align]
[align=left]富文本编辑器的XSS[/align]
[align=left]关于XSS,在@吴中骅的文章中已经非常详细的描述了。而cnode社区中,用户发表话题和回复话题也是用的一个支持markdown格式的富文本编辑器。之前是没有做过任何XSS防范措施的,于是...你可以直接在里面写:[/align]
[align=left]1. <script>alert(123);</script>[/align]
[align=left]2. <div onmouseover="alert(123)"></div>[/align]
[align=left]3. <a href="alert(123);">[/b]123</a>[/align]
[align=left]4. [/align]
[align=left]而markdown格式的内容也没有做URL有效性检测,于是各种样式的XSS又出来了:[/align]
[align=left][xss][1][/align]
[align=left][xss][2][/align]
[align=left]![xss][3][/align]
[1]: alert(123);< /div>
[align=left][2]: http://www.baidu.com/#"onclick='alert(123)'[/align] [align=left][3]: http://www.baidu.com/img.jpg#"onmouseover='alert(123)'[/align] [align=left]在社区这个应用场景下,引入HTML标签只是为了进行一些排版的操作,而其他的样式定义等等都只会让整个界面一团糟,更别说还有潜在的XSS漏洞风险。因此,其实我们是不需要支持用户输入HTML标签来进行内容排版的,一切都可以通过markdown来代替。然后通过简单粗暴的HTML escape,就可以消灭掉直接输入HTML导致的XSS风险。[/align]
[align=left]1. function escape(html) { [/align]
[align=left]2. return html.replace(/&(?!\w+;)/g, '&') [/align]
[align=left]3. .replace(/</g, '<') [/align]
[align=left]4. .replace(/>/g, '>') [/align]
[align=left]5. .replace(/"/g, '"'); [/align]
[align=left]6. }[/align]
[align=left]然而这样粗暴的进行escape,会导致用户输入的代码里面的< > ;这些特殊字符也被转义掉,不能正确显示,需要先将代码段提取出来保存,只转义非代码段的部分。于是这个escape函数变成了这样:[/align]
[align=left]1. function escape(html) { [/align]
[align=left]2. var codeSpan = /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm; [/align]
[align=left]3. var codeBlock = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g; [/align]
[align=left]4. var spans = []; [/align]
[align=left]5. var blocks = []; [/align]
[align=left]6. var text = String(html).replace(/\r\n/g, '\n') [/align]
[align=left]7. .replace('/\r/g', '\n'); [/align]
[align=left]8. text = '\n\n' + text + '\n\n'; [/align]
[align=left]9. texttext = text.replace(codeSpan, function(code) { [/align]
[align=left]10.spans.push(code); [/align]
[align=left]11.return '`span`'; [/align]
[align=left]12.}); [/align]
[align=left]13.text += '~0'; [/align]
[align=left]14.return text.replace(codeBlock, function (whole, code, nextChar) { [/align]
[align=left]15.blocks.push(code); [/align]
[align=left]16.return '\n\tblock' + nextChar; [/align]
[align=left]17.}) [/align]
[align=left]18..replace(/&(?!\w+;)/g, '&') [/align]
[align=left]19..replace(/</g, '<') [/align]
[align=left]20..replace(/>/g, '>') [/align]
[align=left]21..replace(/"/g, '"') [/align]
[align=left]22..replace(/`span`/g, function() { [/align]
[align=left]23.return spans.shift(); [/align]
[align=left]24.}) [/align]
[align=left]25..replace(/\n\tblock/g, function() { [/align]
[align=left]26.return blocks.shift(); [/align]
[align=left]27.}) [/align]
[align=left]28..replace(/~0$/,'') [/align]
[align=left]29..replace(/^\n\n/, '') [/align]
[align=left]30..replace(/\n\n$/, ''); [/align]
[align=left]31.}; [/align]
[align=left]32. [/align]
[align=left]而对于markdown生成的<a>标签和<img>标签中的href属性,必须要做URL有效性检测或者做xss的过滤。这样保证通过markdown生成的HTML代码也是没有XSS漏洞的。[/align]
[align=left]因为XSS的手段确实比较多,见XSS Filter Evasion Cheat Sheet。因此能够做粗暴的HTML escape是最安全的,但是并不是每一个地方都可以通过markdown来代替HTML代码,所以不是每一个地方都能用HTML escape,这个时候就需要其他的手段来过滤XSS漏洞了。[/align]
[align=left]XSS防范只能通过定义白名单的形式,例如只允许<p> <div> <a>标签,只允许href class style属性。然后对每一个可能造成XSS的属性进行特定的过滤。[/align]
[align=left]现有的XSS过滤模块,一个是node-validator, 一个是@雷宗民写的js-xss。[/align]
[align=left]不能够保证XSS模块可以防范住任意的XSS攻击,但是起码能够过滤掉大部分能够想象到的漏洞。node-validator的XSS()仍然有bug,对于<p on="></p>形式的代码,会有双引号不闭合的问题,导致HTML元素测漏。[/align]
[align=left]模版引擎导致的XSS攻击[/align]
[align=left]cnode社区采用的是ejs作为模版引擎,而在ejs中,提供了两种输出动态数据到页面的方法:[/align]
[align=left]<% =data %> //进行xss过滤的输出[/align]
[align=left]<% -data %> //不过滤直接输出[/align]
[align=left]而所有的过滤必须有一个前提: 模版文件中的HTML属性的值等,必须使用双引号。 例如:[/align]
[align=left]1. <img src=\'#\'" reply.author.avatar_url %>' title='<%= reply.author.name %>' />[/align]
[align=left]2. <img src=\'#\'" reply.author.avatar_url %>" title="<%= reply.author.name %>" />[/align]
[align=left]上面两条语句,第一句由于使用的是单引号,用户可以通过构造一个avatar_url中带单引号,来截断src属性,后面就可以随意加javascript代码了。[/align]
[align=left]CSRF攻击[/align]
[align=left]CSRF攻击在node的web开发框架connect和express等中都有了解决方方案。通过在访客的session中存放一个随机的_csrf字段,模版引擎在生成HTML文件的时候将这个_csrf值传递到前端,访客提交的任意POST请求,都必须带上这个字段进行验证,保证了只有当前用户在当前页面上可以进行修改的操作。[/align]
[align=left]然而当页面存在XSS漏洞的时候,CSRF的这种防范措施就成了浮云。恶意攻击者完全可以通过javascript代码,获取到其他用户的_csrf值,并直接模拟用户的POST请求进行服务端数据的更改。[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: