xss是前端经常会遇到的问题,由于页面中的js都执行在同一个上下文中,意味着一旦出现xss漏洞,攻击者就能享有和页面其它部分js同样的权利,能做任何事情

所以一般情况下我们都会对用户输入进行过滤,禁止任何js的执行。但有时由不得不嵌入第三方js,如Google Adsense,这时就只能完全信任它,而没有机制来避免它的恶意行为,另外还有一种方法是通过iframe嵌入,但这样又会遇到不少问题,如高度调整等,而且同样不能避免第三方的恶意行为,如使用ActiveX

是否还有其它方法呢? 文整理了facebook、google、microsoft、yahoo对于这个问题的解决方案,希望能给大家带来一些启发

FBML

FBML是facebook让第三方的html、js、css嵌入到facebook页面的技术,目前在sns中很流行,如空间、人人、51等都在使用它

基本原理

下图是facebook页面展现的一个流程,图片来自http://www.ccheever.com/blog/?p=10

首先由facebook向第三方发起请求,第三方服务返回一个FBML的页面给facebook,然后facebook将其转成html放到facebook的页面中,其中FBML是个类似xhtml的标记语言,它允许使用很多html中的标签,并提供了很多facebook特有的标签,方便第三方开发人员

在FBML转成html的过程中会进行很多安全检查,如给script标签中的变量及函数名都加上前缀,如下面这段代码

  1. function $(str) {
  2. return document.getElementById(str);
  3. }
  4. $("hello");

会被转成如下形式,这里假设第三方应用的id为1,所有变量名都会被加上a1_前缀

  1. function a1_$(a1_str) {
  2. return a1_document.getElementById(a1_str);
  3. }
  4. a12_$("hello");

这样做的好处是第三方的js不会影响到页面中其它js,它只能调用自己内部创建的变量及函数,为了让它具备一定的功能, 可以在运行前给它提供一些全局对象,如前面用到了document全局对象,经过转换后变成了a1_document,只要在运行第三方js前先对这个对象进行赋值,第三方的js就能使用了

  1. vara1_document=newfbjs.main('1');

这样,在fbjs.main对象就代替了第三方js中的document对象,在这里就能进行各种检查,保证第三方js不会影响到页面的其它部分,而且做到了白名单机制,第三方只能调用几个特定的api,而不能进行读写cookie等操作,如fbjs_main实现的一个简单示例

  1. function fbjs_main(appid) {}
  2. fbjs_main.prototype.getElementById=function(id) {
  3. return fbjs_dom.get_instance(document.getElementById(id));
  4. }

可以看到,实际上第三方调用document.getElementById返回的是一个内部的对象,而不是实际的dom节点,这样的好处是可以限制第三方程序的运行,缺点是不能直接访问很多dom节点的属性(如tagName),只能通过调用相应的方法来进行读写,有点类似于jQuery

实现方法

FBML的转换库是开源的,感兴趣的同学可以去libfbml看看,它使用了firefox 2.0.0.4源码中的函数来解析html、css、js

除了解析和代码转换,由于js的动态性,使得很多问题不能通过静态分析发现,因此还需要在运行时进行安全检查,运行库是fbjs,它的实现在fbjs.js中

接下来我们就具体研究一下FBML是如何保证安全的

html/css的转换

首先,FBML中标签的id都加上了前缀,如id=”a”的div转成了

  1. <divid="app_1_a"></div>

同时还对html的属性都进行了严格的检查,避免出现如下类型的代码

  1. <ahref="javascript:alert()">click</a>
  2. <imgsrc="xxx"onerror="alert()"/>

css的转换相对简单些,主要是给selector都加上前缀,将样式都限定到第三方js所属的容器内,如

  1. #app_content_1 .my_class

而对于一些危险的css属性,如expression、behavior、moz-binding都直接滤掉了

为了避免通过position将div移出所属容器,facebook页面中给第三方应用最顶端容器的div加上了overflow:hidden

js的转换

保证js安全主要分为两方面,一是静态分析,如禁止某些函数和变量名加前缀,另一个是进行运行时的检查,如this引用

this引用

this很容易就指到windows上,如在新建对象时少写了个new

  1. function Car() {
  2. this.xx='yyy';
  3. }
  4. varcar=Car();
  5. alert(xx)

所以需要对this进行重新封装,在运行时对其进行检查,将上面的代码转成

  1. function a1_Car() {
  2. ref(this).xx='yyy';
  3. }
  4. vara1_car=a1_Car()

这样就能在运行时对其进行检查,避免它指到window上,ref函数的实现是

  1. function ref(that) {
  2. if (that== window) {
  3. return null;
  4. } else if (that.ownerDocument== document) {
  5. fbjs_console.error('ref called with a DOM object!');
  6. return fbjs_dom.get_instance(that);
  7. } else {
  8. return that;
  9. }
  10. }

with

with无法进行静态分析,因为它临时插入了一个上下文,需要在运行时才能知道取得的变量是哪个,所以FBML中将其禁止了,后面的很多方案也都禁止with语句

危险的属性

js中的某些属性很危险,如可以通过constructor拿到生成对象的构造函数,如下面的代码可以修改Object函数

  1. varo= {};
  2. o.constructor.prototype.xx='xx';

在FBML中将如下几个属性都替换成__unknown__

  • __proto__
  • __parent__
  • caller
  • watch
  • constructor
  • __defineGetter__
  • __defineSetter__

然而由于js的动态性,还可以使用方括号语法来获得这些属性,如之前的代码可以等价于

  1. varo= {};
  2. o['constr' + 'uctor'].prototype.xx='xx';

静态分析是无法发现这类问题的,所以需要类似this那样,对方括号内的属性加上一层运行时的检查

  1. function idx(b) {
  2. return (b instanceof Object || fbjs_blacklist_props[b]) ? '__unknown__' : b;
  3. }

不过上述列表其实还是有风险的,后面的caja、ADsafe等都直接禁止后缀带_的属性,避免后续浏览器升级导致的漏洞

arguments

在老版本的Firefox和IE中,arguments对象可以通过caller取到调用当前函数的函数,会带来很多安全隐患,如ajax回调第三方函数时就把ajax库给暴露了,所以FBML给它加了一层封装,转成arg(arguments),arg函数会将其转成普通数组

  1. function arg(args) {
  2. varnew_args= [];
  3. for (vari=0; i<args.length; i++) {
  4. new_args.push(args[i]);
  5. }
  6. return new_args;
  7. }

eval

因为eval没法进行分析,只能将其禁止,对于需要获取json数据的第三方js,由fbjs提供了Ajax库来转成json对象,不过从代码看这里并没用做相应的安全检查,会造成安全隐患

为了保证eval第三方json时的安全,可以采用json2.js的做法,在eval前检查是否是合法的json

  1. if (!/^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
  2. .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
  3. .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) {
  4. return null;
  5. }

去掉注释

去掉注释似乎是为了减少大小,然而由于IE的条件编译机制,导致注释里还能执行任意JS,所以注释必须去掉,如

  1. /*@cc_on @*/ /*@if (1) alert(document.cookie) @end @*/

另一种就是Firefox对--的错误解析也会导致安全问题

array中的很多方法

在某些浏览器下,array中的很多方法通过call和apply调用时会返回window对象,如下写法在Firefox、Chrome等浏览器中会取到window对象

  1. alert(window=== ([]).sort.call());

所以在fbjs中对这些方法都进行了重写,避免它在运行时的this指向window

  1. Array.prototype.sort= (function(sort) { return function(callback) {
  2. return (this== window) ? null : (callback ? sort.call(this,function(a,b) {
  3. return callback(a,b)}) : sort.call(this));
  4. }})(Array.prototype.sort);

甚至还将reduce、reduceRight直接删掉了

  1. fbjs_main.prototype.getElementById=function(id) {
  2. varappid=fbjs_private.get(this).appid;
  3. return fbjs_dom.get_instance(document.getElementById('app'+appid+'_'+id),appid);
  4. }

dom

为了让第三方js能对页面元素进行控制,fbjs封装了dom的很多方法,并对其进行运行时的检查

getElementById

因为id都加上前缀了,所以fbjs提供给第三方js的api需要加上前缀才能取到

  1. fbjs_main.prototype.getElementById=function(id) {
  2. varappid=fbjs_private.get(this).appid;
  3. return fbjs_dom.get_instance(document.getElementById('app'+appid+'_'+id),appid);
  4. }

getParentNode

为了不让第三方js影响到页面的其它部分,需要在dom节点移动时进行检查,如获取parentNode,要将其限定到第三方应用所属的容器里,不能取高于这个节点的其它元素

createElement、innerHTML

fbjs提供了createElement接口来让第三方js创建元素,并进行检查,只允许创建一些安全的元素

而对于很多开发人员喜欢使用的innerHTML则不容易进行安全检查,因为需要在运行时对html进行解析,fbjs目前只提供两种方式,一种是setInnerFBML,通过FBML中的一个自定义标签<fb:js-string>,让FBML来解析,它的缺点是无法在运行时拼装,另一种是setInnerXHTML,它要求传递的字符串是一个合法的xml形式,直接使用浏览器的XML解析器来解析

location、src、href

有一种风险是可以动态创建一个a标签,设置它的href来生成<a href=”javascript:alert()”>x</a>,所以这些url的属性都需要加上一层判断

  1. fbjs_dom.href_regex= /^(?:https?|mailto|ftp|aim|irc|itms|gopher|\/|#)/;
  2. fbjs_dom.prototype.setHref=function(href) {
  3. href=fbjs_sandbox.safe_string(href);
  4. if (fbjs_dom.href_regex.test(href)) {
  5. fbjs_dom.get_obj(this).href= href;
  6. return this;
  7. } else {
  8. fbjs_console.error(href+' is not a valid hyperlink');
  9. }
  10. }

除此之外还有img的src和location.href

setTimeout、setInterval

setTimeout、setInterval是可以传递字符串来执行的,和eval一样没法进行静态分析,如

  1. vara='ale';
  2. varb='rt()';
  3. setTimeout(a+b, 10);

于是fbjs对这两个函数都进行了封装,限制其只允许传递函数类型

  1. fbjs_sandbox.set_timeout=function(js, timeout) {
  2. if (typeof js != 'function') {
  3. fbjs_console.error('setTimeout may not be used with a string. Please enclose your event in an anonymous function.');
  4. } else {
  5. return setTimeout(js, timeout);
  6. }
  7. }

js代码的执行

fbml解析后的js并不直接放回<script>标签中,而是将它取出,在fbjs中通过eval_global来执行

  1. function eval_global(js) {
  2. varobj=document.createElement('script');
  3. obj.type='text/javascript';
  4. try {
  5. obj.innerHTML=js;
  6. } catch(e) {
  7. obj.text=js;
  8. }
  9. document.body.appendChild(obj);
  10. }

为何要这么做呢? 我能想到的一个原因是js字符中script标签会导致漏洞,不好进行静态分析,如下面的代码会在IE、Chrome下执行alert

  1. <script>
  2. vara="</script><script>alert()</script>";
  3. </script>

不过因为FBML使用了firefox的引擎来解析html,所以直接在字符串中第一个</script>标签就截断了

隐蔽的问题

了解fbml的解决方案后,感觉挺似乎很完美,然而实际上远没有那么简单,Facebook script injection vulnerabilities上报了很多Facebook的漏洞,如Firefox中支持的E4X语法,因为Facebook是基于firefox2的解析引擎,所以导致解析时没有发现问题

  1. <script>
  2. <xx="
  3. x" {alert('any javascript')}="x"
  4. />
  5. </script>

在fbjs中有大量这样case by case的漏洞修复,涉及到浏览器特性的问题是无法预测的,尤其是浏览器解析时对html的容错机制(这也是html5重点解决的一个问题,感兴趣的同学可以看看其中的第8章),不过总的来说fbml的解决方案还是很不错的,也经受了大量的线上考验

Microsoft Web Sandbox

Microsoft Web Sandbox是微软提出的一种方案,它最大的特点就是引入了虚拟机的思想,将代码全部转成了虚拟机中的调用,将很多安全检查的工作放到运行时去解决, 在这个虚拟机中将所有html标签,以及所有的js对象和dom的调用都进行了封装,将第三方代码完全和外部环境隔离开

实现细节

我们来看看它的具体是怎么实现的,首先html源码经过转换后全都成了js变量,如下html

  1. <html>
  2. <body>
  3. <aid="b"href="javascript:alert()">click</a>
  4. </body>
  5. </html>

经过转换后会变成

  1. varsettings= {};
  2. varheaderJavaScript=
  3. function(a)
  4. {
  5. varb=a.gw(this), //获取虚拟机内部的全局对象
  6. c=a.g,        //用于取得对象的属性,如后面用来取document
  7. d=a.i,        //用于调用一个方法,如后面用来调用alert
  8. e=a.f,        //用于将函数封装起来,对函数的运行做监控,如避免死循环和频繁alert等问题
  9. f=c(b,"document");
  10. //接下来通过调用initializeHTML来生成html
  11. d(f,"initializeHTML",[[{"body":{"c":[," ",{"a":{"a":{"href":e(function()
  12. {
  13. d(b,"alert")
  14. }),"id":"b"},"c":[,"click"]}}," "]}},{"#text":{"c":[" "]}}]])
  15. };
  16. varmetadata= {"author":"","description":"","imagepath":"","title":"","preferredheight":0,"preferredwidth":0,"location":"","icon":"","base":{"href":"","target":""}};
  17. $Sandbox.registerCode(headerJavaScript, "1", settings, metadata);
  18. varSandboxInstance=new$Sandbox(document.getElementById('g_1_0_inst'), $Policy.Canvas, "1");
  19. SandboxInstance.initialize();

对于html和css,都进行了Tokenization,转成了json的形式,注意这里并没有像fbml那样进行代码转换,而是等到了运行时再拼装,如将

  1. <aid="b"href="javascript:alert()">click</a>

转成了json形式的变量

  1. {"a":
  2. {
  3. "a":{
  4. "href":e(function() {
  5. d(b,"alert")
  6. }),
  7. "id":"b"
  8. },
  9. "c":[,"click"]
  10. }
  11. }

在最后由运行库来拼接成html,如

  1. <aid="cst9"href="javascript:$Sandbox.Instances['__S__8'].__exechref__(0)">click</a>

这样的好处是能轻松迁移和多实例,因为生成后的代码相当于虚拟机的指令,而不像fbml那样将都和特定id绑死了,websandbox能同时运行几个相同的第三方代码,只需再new一个$sandbox就可以了

websandbox的虚拟机机制使得让它能进行大量的运行时检查,这样就能解决死循环和无限alert的问题,这都是其它方案无法解决的,也是目前这几种方案中功能最强大的,可惜解析部分并没有开源,只将运行库开源了,解析只能手工执行

caja

caja是google提出的方案,和websandbox很类似, 也是将js的操作封装成了各种get、set、call,通过封装一层函数来保证安全,但caja并没有将html封装到运行库中,这导致了很多限制,如不能使用onclick,如下面的代码

  1. <divid="star"onclick="alert()"></div>
  2. <scripttype="text/javascript">
  3. varstar=document.getElementById("star");
  4. star.innerHTML="<xx>";
  5. </script>

经转换后变成了

  1. <divid="id_2___"></div>
  2. <script>
  3. {
  4. ___.loadModule({
  5. 'instantiate': function (___, IMPORTS___) {
  6. varmoduleResult___= ___.NO_RESULT;
  7. var $v=___.readImport(IMPORTS___, '$v', {
  8. 'getOuters': { '()': {} },
  9. 'initOuter': { '()': {} },
  10. 'so': { '()': {} },
  11. 'cm': { '()': {} },
  12. 'ro': { '()': {} },
  13. 's': { '()': {} }
  14. });
  15. var $dis;
  16. $dis= $v.getOuters();
  17. $v.initOuter('onerror');
  18. {
  19. var el___;
  20. varemitter___=IMPORTS___.htmlEmitter___;
  21. el___=emitter___.byId('id_1___');
  22. emitter___.setAttr(el___, 'id', 'star-' + IMPORTS___.getIdClass___());
  23. el___=emitter___.finish();
  24. }
  25. try {
  26. {
  27. $v.so('star', $v.cm($v.ro('document'), 'getElementById', [ 'star' ]));
  28. moduleResult___= $v.s($v.ro('star'), 'innerHTML', '\x3cxx\x3e');
  29. }
  30. } catch (ex___) {
  31. ___.getNewModuleHandler().handleUncaughtException(ex___,
  32. $v.ro('onerror'), 'test.html', '3');
  33. }
  34. {
  35. IMPORTS___.htmlEmitter___.signalLoaded();
  36. }
  37. return moduleResult___;
  38. },
  39. 'includedModules': [ ],
  40. 'cajolerName': 'com.google.caja',
  41. 'cajolerVersion': '4097',
  42. 'cajoledDate': 1275642131084
  43. });
  44. }
  45. </script>

ADsafe

和前面几种方案不同,Douglas Crockford写的ADsafe
采用了另一种方式,它并没有进行代码转换,而是采取了验证的方法,第三方的JS需要先经过jslint的验证才能允许执行,这些验证包括

  • 不得使用除了ADsafe提供以外的任何全局对象,对于Array Math等提供了有限的范围
  • 不能使用this arguments eval with
  • 不能使用arguments callee caller constructor eval prototype stack unwatch valueOf watch
  • 不能使用以_开头或结尾的string
  • 不能使用[]
  • 不能使用Date和Math.random,这样保证widget的运行不会有随机性

ADsafe不进行代码转换避免了很多运行时的性能开销,然而却导致了大量的限制,第三方开发人员有一定的学习成本,如没法使用[],而必须使用ADSAFE.get ADSAFE.set

不过ADsafe最大的好处就是可以不依赖后端,很适合做简单的widget及嵌入第三方的广告,管理员只需要将第三方js代码拿到jslint中验证一下即可,不了解js也能进行审核,而且因为禁止了Date和Math.random,使得js代码的执行结果是稳定的,下面演示了一个简单例子

  1. <divid="WIDGETNAME_"><inputtype=text/></div>
  2. <script>
  3. "use strict";
  4. ADSAFE.go("WIDGETNAME_", function (dom) {
  5. varinput=dom.q("input_text");
  6. });
  7. </script>

小结

  • FBML简单实用的,也经过了线上成熟的考验,对于做类似sns的开放平台推荐使用,然而fbjs的api用起来不够方便,开发人员由一定的学习成本,开发调试不容易
  • websandbox的虚拟机思想很强大,给开发人员很大的自由,值得学习和借鉴,可惜没有开源导致无法使用
  • ADsafe适合做一些简单的应用,然而限制实在太多,第三方开放人员可能会很不适应
  • 不推荐使用caja,它的实现较为复杂,很容易导致各种问题,后期也不好维护

值得一提的是,从目前实际使用的情况看,第三方开发人员其实更愿意使用iframe,因为它没有学习成本,且更灵活自由,权衡之下也许会发现这个最简单方案才是最简单可依赖的

参考

  • Facebook script injection vulnerabilities

    • 报了很多facebook的漏洞
  • AttackVectors
    • caja的wiki中列举了很多攻击的手法
【本文首发于:百度泛用户体验】http://www.baiduux.com/blog/2010/07/07/js-safe/
【关注百度技术沙龙】
本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/746900,如需转载请自行联系原作者

如何安全地嵌入第三方js – FBML/caja/sandbox/ADsafe简介相关推荐

  1. [JavaScript]如何安全地嵌入第三方js – FBML/caja/sandbox/ADsafe简介

    原文地址:http://www.baiduux.com/blog/2010/07/07/js-safe/ xss是前端经常会遇到的问题,由于页面中的js都执行在同一个上下文中,意味着一旦出现xss漏洞 ...

  2. Debug javascript inside jsp page 调试jsp嵌入的js

    2019独角兽企业重金招聘Python工程师标准>>> 当我用chrome调试jsp 嵌入的js程序时, 在 developer tools  的source中无法找到对应的jsp ...

  3. Cloud for Customer里的使用的一个第三方js库,用于gzip处理

    可以从Chrome开发者工具里观察到这个第三方js文件的加载和内容: [外链图片转存失败(img-b2u1ArC4-1566695025554)(https://upload-images.jians ...

  4. Vue之引用第三方JS插件,CKPlayer使用

    前言: 不管是VUE还是Angular,有时候我们需要使用到一些js插件,但是的源库中并没有相应的插件包,这个时候我们需要如何引入并且使用呢?这个问题其实非常简单,接下来我以VUE为例写给大家介绍一下 ...

  5. http://www.baidu.com/cb.php?,存在劫持风险的第三方JS地址

    存在劫持风险的第三方JS地址 发布日期:2019-10-11 https://inxx.in/v20v2/?/e00555b779fe42a0/DRENBwgP/gXHBv4= https://inx ...

  6. vue 导入第三方js实例对象

    第一步: 因为vue是单页面引用,所有要在一开始加载页面的时候将js库给引入进来,无论是本地的文件还是网络文件,都需要引入.vue的单页面入口就在根目录中的index.html中. 第二步 在配置文件 ...

  7. 微信小程序使用第三方库(第三方js)问题

    比如很多人会有这样的问题: 小程序怎样引用第三方js呢? 第三方js是封装好的类库 想引用进来实例化使用 这个帖子就综合一下所有相关的知识,做一个整合,以便大家能够集中了了解:我觉得这个还是应该让大家 ...

  8. Vue 中引用第三方js总结

    vue中引用第三方js总结 By:授客 QQ:1033553122 实践环境 win10 Vue 2.9.6 本文以引用jsmind为例,讲解怎么在vue中引用第三方js类库 基础示例 1.把下载好的 ...

  9. uni-app 项目引入第三方js插件,单个js文件引入成功,调用该插件方法

    通过"vue项目引入第三方js插件,单个js文件引入成功,使用该插件方法时报错(问题已解决)"成功移到UNI-APP上 方法一: 引用网址:https://www.cnblogs. ...

最新文章

  1. 隐私全无!错发1700多条Alexa录音,上报后亚马逊淡定回应是“个人错误”
  2. python自学网站有哪些-小白如何入门Python? 制作一个网站为例
  3. delphi Bpl 学习杂记
  4. 科大星云诗社动态20210406
  5. P6563-[SBCOI2020]一直在你身旁【dp,单调队列】
  6. 模型压缩:模型量化打怪升级之路-工具篇
  7. MyBatis-面试题
  8. 金山云服务器内网带宽,性能提升40%!第三代金山云服务器全面覆盖不同企业计算力需求...
  9. bootstrap切换tab页局部刷新_bootstrap在 刷新页面,tab选择页面不会改变。
  10. 京东一面:高并发下,如何保证分布式唯一全局 ID 生成?
  11. eclipse 反编译插件 图文
  12. Gauss-Newton法matlab求解
  13. Linux查看端口命令:netstat -tln
  14. 战国李悝的“识人五法
  15. win7系统wifi没有网络连接到服务器,Win7连不上WiFi怎么办 windows7系统恢复无线网络连接图文教程详解...
  16. 小米3刷android 6.0,安卓6.0版MIUI7曝光:小米3/小米4/小米Note将尝鲜
  17. latex数字引用参考文献
  18. 安卓Activity转场动画
  19. Jmeter 4.0+高分屏参数自动设置脚本
  20. 真约数求法 c语言,怎样求真约数

热门文章

  1. 程序员必备网站和工具
  2. 保护您的 ASP.NET 应用程序
  3. 《思维导图应用实战》画出你的思维
  4. linux+显卡超频软件,安装和使用GreenWithEnvy在Linux上超频Nvidia显卡
  5. 惊爆:当Python代码遇到zip解压炸弹,未做防护的你后悔莫及!
  6. 关于PCA主成分分析与KL变换
  7. 数据分析的工作内容是什么,数据分析师、数据产品经理和数据挖掘工程师三个岗位之间,有什么联系和区别?
  8. VC++ 如何让 MessageBox或AfxMessageBox 按钮显示英文或其他语言
  9. 数据通信技术_数据通信相关汇总
  10. PKplayer(P2P播放器)开源P2P播放器综述