前端安全知识以及防范
基础
XSS就是让浏览器执行想插入的js。那么如何发现这些漏洞呢?只要有输入和输出的地方都伴随着漏洞的产生,下面介绍基础的XSS漏洞分析。
XSS攻击并非一个<sc+ript+>al+ert('xs+s漏洞')</script>(这里请看源码)语句就可以检测。
<script>alert('xss漏洞')</script>
-
XSS初级测试(反射型XSS):
反射型XSS需要欺骗用户点击才能触发XSS代码。 -
输入框是否过滤<>’”/等字符,输入框中输入这些字符后提交,查看网页源码中这四个字符有没有被过滤;
-
在输入框或者url的参数中输入<img onerror=alert(1) src=1>后,如果出现界面错误,那就提BUG吧;
-
使用eval()函数,该函数执行js代码,输入框输入或者url参数等于eval("alert('Hello world')”),是否能够执行。
-
XSS中级测试:
-
宽字节注入漏洞,GB系列这些编码格式只有两个字节,引起的安全问题是吃一个ASCII字符的现象像一个<input type=“text” value=“”> 的语句,我们要做的是闭合双引号(%22),但是如果value输入%22的话就会被转义为%5C%22,如果输入%C0%22 /><script>alert(1)</script>的话,过滤器发现%22加入转义(%5C),然而%C0吃掉了%5C,这样就闭合了前面的双引号。
-
存储型XSS,和前面的反射型XSS不同,是将XSS代码提交服务器中并存储下来,其他人在访问能拿到该字段的页面就会触发XSS代码执行。我找到了一个乌云上淘宝宝贝详情的存储型XSS。
-
测试建议:
-
必须对所以交互的输入字符、http请求头部变量、cookie变量等检测字符和html关键字段;
-
前端或者客户端做了过滤还不够,服务端也要进行过滤;
-
为了防止存储型XSS产生,对存入数据库的字段不光要在输入字符时进行过滤和检测,还要在数据库字段输出到页面时进行过滤和检测;
-
网上已有的XSS代码:XSS代码;
-
乌云上很多XSS漏洞的真实例子,大家测试之余可以去搜来看看,乌云网站。
深入
XSS攻击的本质是执行代码,根据代码在页面中的执行环境可分为3种:
1.存储型XSS
用户访问正常的URL,触发代码执行。
大部分情况是由于过滤不完全或未转码,直接渲染到页面中,导致代码执行。
2.DOM型XSS
用户访问正常的URL,触发代码执行。
前端人员经常把数据埋在Dom节点中,然后通过JS代码获取内容,渲染,然后插入到页面中,导致代码被执行。
3.反射型XSS
用户访问经过特殊构造的URL,触发代码执行.
常通过垃圾邮件传播,邮件内容大多是具有诱惑性的内容,引诱点击。QQ被盗后,空间说说等经常出现不明链接等等。
存储型XSS漏洞根据代码执行位置不同,又可分为:
1.HTML存储型
发表文章,填写资料,留言等数据提交到后台,过滤不全,在用户访问页面的时候,恶意内容渲染到页面中,导致攻击。
2.CSS存储型
常见于淘宝店铺装修,网页换肤,背景图等,这些地方都需要用户提交CSS样式,甚至引入外部JS,如果对输入没有做严格的校验,直接渲染到页面,极易产生安全隐患
淘宝商品详情页的评论记录,交易记录都曾被非法篡改。
攻击的方式多种多样,CSS支持unicode,很多漏洞直接采用编码的方式绕过安全漏洞。
<div style="width:expression(if(!window.x){alert(1);window.x=1})"></div>
<link rel=stylesheet href=data:,*%7bx:expression(if(!window.x)%7balert(1);window.x=1%7d)%7d />
<div style="width:expression(if(!window.x){alert(1);window.x=1})"></div>
<table background="javascript:alert(/xss/)"></table>'
3.JS存储型
这种比较少见,有时候会前端会使用eval,jQuery.globalEval,前者在代码中比较常见,1688这边,去年安全部集中处理过一批这些漏洞。
用户一般难以直接输入JS代码,但有时候,前端开发人员会要求将数据直接转成JS代码,放入到页面中,方便获取,如果内容未经过安全处理,攻击者就会尝试中断正常的代码逻辑,插入攻击代码。
Dom型XSS攻击
'''
前端开发者,经常将数据存放在Dom节点,在某个时候,获取节点的数据,然后插入到页面中,插入到页面时,常使用jQuery('body').html('<script>alert(3)</script>') 操作,导致代码被执行。
反射型XSS攻击
用户访问经过特殊构造的URL,URL参数中会携带攻击代码。
现在我们假设一种攻击场景:私信
- 攻击者A,利用私信功能向用户B发送私信,私信内容存在攻击代码(JS)
- 用户浏览私信,浏览的过程中,JS代码在同域名下,毫无顾忌地执行,相当于开发方自己的代码
- 用户B成为受害者
- 攻击代码可以将受害者B的其它私信提交到自己的内容收集平台
- 攻击代码自动与好友发送私信,私信内容与上述一样。从而产生新的受害者。。。。
- 如此循环
可以看到,用户B的私密消息,完全被暴露。
很多网站产品会让用户填写个人资料,身份证,姓名等等,一旦发生此类攻击,用户的信息也就被泄露了。
目前网站依赖token做CSRF校验,其有效的前提是不存在XSS漏洞,token不会泄露。
防御
在编写代码的时候,尽量站在攻击者角度去编写代码。
其实记住两点就行:
-
不要信任用户输入的任何数据
-
输入、输出过滤
作为防御方,我们尽量够做的其实很有限,不同的场景,可能有不同的漏洞产生,提高自身的安全意识,编写安全代码是我们目前能够做的。
举个小例子,判断一个URL是否是1688域名,在前端提交的时候会做判断,后台也会做判断。
正则可能如下:
/http://.*.1688.com/i
大家可以思考一下这个正则有问题吗? 前端可能写对了,后台开发人员呢?
在攻击发生时,往往是多个类型XSS互相利用,最后产生较大的安全问题。还有就是新技术的应用,比如NodeJs,在安全方面的处理还是不够,这种在线上也存在相同的问题。目前也有新的CSP策略,在高级浏览器(IE10,)中可以起到一定的安全防御作用,但是就当前环境来说,低版本浏览器,我们还没法放弃。
一些例子
2011年,新浪微博XSS蠕虫事件:攻击者利用广场的一个反射性XSS URL,自动发送微博、私信,私信内容又带有该XSS URL,导致病毒式传播。
09年twitter也发生过类似事件。
乌云平台也有很多此类例子,可以搜索关键词XSS
- 安全平台自己也有漏洞 ,rank 10,http://wooyun.org/bugs/wooyun-2010-059832
- 阿里云账号泄露 ,rank 20, http://wooyun.org/bugs/wooyun-2010-054102
- 腾讯微博 http://www.wooyun.org/bugs/wooyun-2010-020167
- 百度贴吧 http://www.wooyun.org/bugs/wooyun-2010-053221 太多了。。。。。。
有时候,漏洞的危害性可能不是很大,但是极易引起用户反弹,公关问题,。
实践 -- show me the code
前端XSS
前端XSS往往由于前端层面用innerHTML或者$('#id').html('...')方法时,没有对用户输入的内容和正常的HTML代码进行隔离,从而让黑客可以轻松获取JS执行权限。
那么,标准的前端逻辑应该怎么写?
$('#id').html('![](test.jpg)');
如果我们使用的是模板,例如handbar,由于handbar默认会进行转义,因此默认即是安全的:
理论上,我们建议能使用模板的场景尽量使用模板,能为我们节省大量的代码量,并且同时也能保证我们代码不会出现XSS漏洞。
对于使用underscore的同学,请留意下默认的是不转义的,例如:
hello: <%= name %>
如果需要转义,请务必使用:
hello: <%- name %>
注:我们对underscore进行了改造,将这两个标识进行了翻转,经过改造后,也符合了默认转义的原则。
成果
在这之外,我们还有什么手段解决前端XSS问题呢?
jQuery目前已经无孔不入,几乎找不着没有使用jQuery的业务场景。
我们的方案是在外部对jQuery进行了hack,将所有html代码进行安全过滤,以实现前端防火墙的功能。
过滤逻辑如下:将所有js代码过滤为无效代码, 例如:
'''
<img src="" onerror="alert(1)"> <script>alert(1)</script>
'''
会替换为:
'''
<img src="" onerror0="alert(1)"> <script0>alert(1)</script>
'''
经过此过滤,XSS就再也无法猖狂了!
并且,此方案还有1个优点,由于此方案是全站底层进行防火部署,因此过滤算法可以随时进行优化升级,以解决今后出现新的XSS漏洞。
不过,此方案并非没有缺陷。由于此方案屏蔽了所有的JS代码,因此对于旧逻辑中所有HTML和JS混合的场景都需要进行改造。
但是,根据实际部署过程来看,这种混合场景占比非常非常少。
我如何部署?
security.js
'''js
(function (win) {
var security = {}; // 初始事件正则
var objOnEvents = {};
var reAllEvents; // init all events
var doc = document;
var arrDoms = [window, doc.createElement("form")];
try {
arrDoms.push(doc.createElement("img"));
arrDoms.push(doc.createElement("iframe"));
arrDoms.push(doc.createElement("object"));
arrDoms.push(doc.createElement("embed"));
arrDoms.push(doc.createElement("audio"));
} catch (e) {
}
var dom;
var key;
for (var i = 0, c = arrDoms.length; i < c; i++) {
dom = arrDoms[i];
for (key in dom) {
if (/^on/.test(key)) {
objOnEvents[key.substring(2)] = 1;
}
}
}
var arrAllEvents = [];
for (key in objOnEvents) {
arrAllEvents.push(key);
}
if (arrAllEvents.length > 0) {
reAllEvents = new RegExp('(['"\\s\\/]on(?:' + arrAllEvents.join('|') + '))\\s*=', 'ig'); } else { reAllEvents = /(['"
\s/]on(\w+))\s=/ig;
} // HTML转义
security.encodeHTML = function (str) {
return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '
').replace(/</g, '<').replace(/>/g, '>');
}; // 过滤html中可能引发XSS的代码
security.htmlFilter = function (str, args) {
var reBadTags = /<(script|link|frame)([^\w])/ig;
var reBadAttrs = /['"\s\/](srcdoc)\s*=/ig; var reBadSrc = /[\'"
\s/]src\s=\s['"]?\s(javascript:|data\s:\stext/html)/ig;
str = String(str); // 过滤script & link...
str = str.replace(reBadTags, '<$10$2'); // 过滤on事件
str = str.replace(reAllEvents, '$10='); // 过滤srcdoc属性
str = str.replace(reBadAttrs, ' $10='); // 过滤危险src: javascript: data:
str = str.replace(reBadSrc, ' src0="');
return str;
}; // hack jQuery
security.hookJquery = function ($) {
if ($ && !$.fn.rawHtml) { // 1. html->rawHtml
$.fn.rawHtml = $.fn.html;
$.fn.html = function (value) {
var args = Array.prototype.slice.call(arguments);
if (typeof value === 'string' && $.htmlFilter) {
args[0] = security.htmlFilter(value, arguments);
}
return $.fn.rawHtml.apply(this, args);
};
}
};
win.security = security;
})(window);
'''
test.html
''' html
<div id="test"></div>
<script type="text/javascript" src="js/jquery-1.10.2.js"></script>
<script type="text/javascript" src="security.js"></script>
<script type="text/javascript">
security.hookJquery($); $.htmlFilter = true; $('#test').html('<img src="" onerror="alert(1)">')
</script>
框架底层可以直接将jQuery进行hook,但是具体过滤逻辑是否开启取决于$.htmlFilter是否打开。
在部署过程中,请务必要保证业务代码全部改造为纯HTML的情况下才能开启开关,否则会造成线上故障