當前位置:編程學習大全網 - 源碼下載 - 如何安全地嵌入第三方js

如何安全地嵌入第三方js

xss是前端經常會遇到的問題,由於頁面中的js都執行在同壹個上下文中,意味著壹旦出現xss漏洞,攻擊者就能享有和頁面其它部分js同樣的權利,能做任何事情

所以壹般情況下我們都會對用戶輸入進行過濾,禁止任何js的執行。但有時由不得不嵌入第三方js,如Google Adsense,這時就只能完全信任它,而沒有機制來避免它的惡意行為,另外還有壹種方法是通過iframe嵌入,但這樣又會遇到不少問題,如高度調整等,而且同樣不能避免第三方的惡意行為,如使用ActiveX

是否還有其它方法呢? 文整理了facebook、google、microsoft、yahoo對於這個問題的解決方案,希望能給大家帶來壹些啟發

FBML

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

基本原理

下圖是facebook頁面展現的壹個流程,圖片來自/blog/?p=10

首先由facebook向第三方發起請求,第三方服務返回壹個FBML的頁面給facebook,然後facebook將其轉成html放到facebook的頁面中,其中FBML是個類似xhtml的標記語言,它允許使用很多html中的標簽,並提供了很多facebook特有的標簽,方便第三方開發人員

在FBML轉成html的過程中會進行很多安全檢查,如給script標簽中的變量及函數名都加上前綴,如下面這段代碼

function $(str) {

return document.getElementById(str);

}

$("hello");

會被轉成如下形式,這裏假設第三方應用的id為1,所有變量名都會被加上a1_前綴

function a1_$(a1_str) {

return a1_document.getElementById(a1_str);

}

a12_$("hello");

這樣做的好處是第三方的js不會影響到頁面中其它js,它只能調用自己內部創建的變量及函數,為了讓它具備壹定的功能, 可以在運行前給它提供壹些全局對象,如前面用到了document全局對象,經過轉換後變成了a1_document,只要在運行第三方js前先對這個對象進行賦值,第三方的js就能使用了

var a1_document = new fbjs.main('1');

這樣,在fbjs.main對象就代替了第三方js中的document對象,在這裏就能進行各種檢查,保證第三方js不會影響到頁面的其它部分,而且做到了白名單機制,第三方只能調用幾個特定的api,而不能進行讀寫cookie等操作,如fbjs_main實現的壹個簡單示例

function fbjs_main(appid) {}

fbjs_main.prototype.getElementById = function(id) {

return fbjs_dom.get_instance(document.getElementById(id));

}

可以看到,實際上第三方調用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轉成了

<div id="app_1_a"></div>

同時還對html的屬性都進行了嚴格的檢查,避免出現如下類型的代碼

<a href="javascript:alert()">click</a>

<img src="xxx" onerror="alert()" />

css的轉換相對簡單些,主要是給selector都加上前綴,將樣式都限定到第三方js所屬的容器內,如

#app_content_1 .my_class

而對於壹些危險的css屬性,如expression、behavior、moz-binding都直接濾掉了

為了避免通過position將div移出所屬容器,facebook頁面中給第三方應用最頂端容器的div加上了overflow:hidden

js的轉換

保證js安全主要分為兩方面,壹是靜態分析,如禁止某些函數和變量名加前綴,另壹個是進行運行時的檢查,如this引用

this引用

this很容易就指到windows上,如在新建對象時少寫了個new

function Car() {

this.xx = 'yyy';

}

var car = Car();

alert(xx)

所以需要對this進行重新封裝,在運行時對其進行檢查,將上面的代碼轉成

function a1_Car() {

ref(this).xx = 'yyy';

}

var a1_car = a1_Car()

這樣就能在運行時對其進行檢查,避免它指到window上,ref函數的實現是

function ref(that) {

if (that == window) {

return null;

} else if (that.ownerDocument == document) {

fbjs_console.error('ref called with a DOM object!');

return fbjs_dom.get_instance(that);

} else {

return that;

}

}

with

with無法進行靜態分析,因為它臨時插入了壹個上下文,需要在運行時才能知道取得的變量是哪個,所以FBML中將其禁止了,後面的很多方案也都禁止with語句

危險的屬性

js中的某些屬性很危險,如可以通過constructor拿到生成對象的構造函數,如下面的代碼可以修改Object函數

var o = {};

o.constructor.prototype.xx = 'xx';

在FBML中將如下幾個屬性都替換成__unknown__

__proto__

__parent__

caller

watch

constructor

__defineGetter__

__defineSetter__

然而由於js的動態性,還可以使用方括號語法來獲得這些屬性,如之前的代碼可以等價於

var o = {};

o['constr' + 'uctor'].prototype.xx = 'xx';

靜態分析是無法發現這類問題的,所以需要類似this那樣,對方括號內的屬性加上壹層運行時的檢查

function idx(b) {

return (b instanceof Object || fbjs_blacklist_props[b]) ? '__unknown__' : b;

}

不過上述列表其實還是有風險的,後面的caja、ADsafe等都直接禁止後綴帶_的屬性,避免後續瀏覽器升級導致的漏洞

arguments

在老版本的Firefox和IE中,arguments對象可以通過caller取到調用當前函數的函數,會帶來很多安全隱患,如ajax回調第三方函數時就把ajax庫給暴露了,所以FBML給它加了壹層封裝,轉成arg(arguments),arg函數會將其轉成普通數組

function arg(args) {

var new_args = [];

for (var i = 0; i < args.length; i++) {

new_args.push(args[i]);

}

return new_args;

}

eval

因為eval沒法進行分析,只能將其禁止,對於需要獲取json數據的第三方js,由fbjs提供了Ajax庫來轉成json對象,不過從代碼看這裏並沒用做相應的安全檢查,會造成安全隱患

為了保證eval第三方json時的安全,可以采用json2.js的做法,在eval前檢查是否是合法的json

if (!/^[/],:{}/s]*$/.test(data.replace(///(?:["////bfnrt]|u[0-9a-fA-F]{4})/g, "@")

.replace(/"[^"///n/r]*"|true|false|null|-?/d+(?:/./d*)?(?:[eE][+/-]?/d+)?/g, "]")

.replace(/(?:^|:|,)(?:/s*/[)+/g, "")) ) {

return null;

}

去掉註釋

去掉註釋似乎是為了減少大小,然而由於IE的條件編譯機制,導致註釋裏還能執行任意JS,所以註釋必須去掉,如

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

另壹種就是Firefox對--的錯誤解析也會導致安全問題

array中的很多方法

在某些瀏覽器下,array中的很多方法通過call和apply調用時會返回window對象,如下寫法在Firefox、Chrome等瀏覽器中會取到window對象

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

所以在fbjs中對這些方法都進行了重寫,避免它在運行時的this指向window

Array.prototype.sort = (function(sort) { return function(callback) {

return (this == window) ? null : (callback ? sort.call(this,function(a,b) {

return callback(a,b)}) : sort.call(this));

}})(Array.prototype.sort);

甚至還將reduce、reduceRight直接刪掉了

Array.prototype.reduce = null;

Array.prototype.reduceRight = null;

dom

為了讓第三方js能對頁面元素進行控制,fbjs封裝了dom的很多方法,並對其進行運行時的檢查

getElementById

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

fbjs_main.prototype.getElementById = function(id) {

var appid = fbjs_private.get(this).appid;

return fbjs_dom.get_instance(document.getElementById('app'+appid+'_'+id),appid);

}

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的屬性都需要加上壹層判斷

fbjs_dom.href_regex = /^(?:https?|mailto|ftp|aim|irc|itms|gopher|//|#)/;

fbjs_dom.prototype.setHref = function(href) {

href = fbjs_sandbox.safe_string(href);

if (fbjs_dom.href_regex.test(href)) {

fbjs_dom.get_obj(this).href = href;

return this;

} else {

fbjs_console.error(href+' is not a valid hyperlink');

}

}

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

setTimeout、setInterval

setTimeout、setInterval是可以傳遞字符串來執行的,和eval壹樣沒法進行靜態分析,如

var a = 'ale';

var b = 'rt()';

setTimeout(a+b, 10);

於是fbjs對這兩個函數都進行了封裝,限制其只允許傳遞函數類型

fbjs_sandbox.set_timeout = function(js, timeout) {

if (typeof js != 'function') {

fbjs_console.error('setTimeout may not be used with a string. Please enclose your event in an anonymous function.');

} else {

return setTimeout(js, timeout);

}

}

js代碼的執行

fbml解析後的js並不直接放回<script>標簽中,而是將它取出,在fbjs中通過eval_global來執行

function eval_global(js) {

var obj = document.createElement('script');

obj.type = 'text/javascript';

try {

obj.innerHTML = js;

} catch(e) {

obj.text = js;

}

document.body.appendChild(obj);

}

為何要這麽做呢? 我能想到的壹個原因是js字符中script標簽會導致漏洞,不好進行靜態分析,如下面的代碼會在IE、Chrome下執行alert

<script>

var a = "</script><script>alert()</script>";

</script>

不過因為FBML使用了firefox的引擎來解析html,所以直接在字符串中第壹個</script>標簽就截斷了

隱蔽的問題

了解fbml的解決方案後,感覺挺似乎很完美,然而實際上遠沒有那麽簡單,Facebook script injection vulnerabilities上報了很多Facebook的漏洞,如Firefox中支持的E4X語法,因為Facebook是基於firefox2的解析引擎,所以導致解析時沒有發現問題

<script>

<x x="

x" {alert('any javascript')}="x"

/>

</script>

在fbjs中有大量這樣case by case的漏洞修復,涉及到瀏覽器特性的問題是無法預測的,尤其是瀏覽器解析時對html的容錯機制(這也是html5重點解決的壹個問題,感興趣的同學可以看看其中的第8章),不過總的來說fbml的解決方案還是很不錯的,也經受了大量的線上考驗

Microsoft Web Sandbox

Microsoft Web Sandbox是微軟提出的壹種方案,它最大的特點就是引入了虛擬機的思想,將代碼全部轉成了虛擬機中的調用,將很多安全檢查的工作放到運行時去解決, 在這個虛擬機中將所有html標簽,以及所有的js對象和dom的調用都進行了封裝,將第三方代碼完全和外部環境隔離開

  • 上一篇:安康到無錫源代碼
  • 下一篇:CF王者瞳閉月羞花第四個屬性怎麽解鎖?第四個屬性解鎖方法分享
  • copyright 2024編程學習大全網