摘要:本文由云社區(qū)發(fā)表作者市面上現(xiàn)在流行兩種沙箱模式一種是使用還有一種是直接在頁(yè)面上使用進(jìn)行執(zhí)行。接下來(lái)我們來(lái)一步一步分析如果做到在前端的沙箱文末看俺有沒有心情放一個(gè)彩蛋吧。等價(jià)于所以第一步改寫上面的將里面變量的獲取途徑控制在自己的手里。
本文由云+社區(qū)發(fā)表作者:ivweb villainthr
市面上現(xiàn)在流行兩種沙箱模式,一種是使用iframe,還有一種是直接在頁(yè)面上使用new Function + eval進(jìn)行執(zhí)行。 殊途同歸,主要還是防止一些Hacker們 吃飽了沒事干,收別人錢來(lái) Hack 你的網(wǎng)站。 一般情況, 我們的代碼量有60%業(yè)務(wù)+40%安全. 剩下的就看天意了。接下來(lái),我們來(lái)一步一步分析,如果做到在前端的沙箱.文末 看俺有沒有心情放一個(gè)彩蛋吧。
直接嵌套這種方式說(shuō)起來(lái)并不是什么特別好的點(diǎn)子,因?yàn)樾枰ㄙM(fèi)比較多的精力在安全性上.
eval執(zhí)行最簡(jiǎn)單的方式,就是使用eval進(jìn)行代碼的執(zhí)行 eval("console.log("a simple script");");
但,如果你是直接這么使用的話, congraduations... do die... 因?yàn)?eval 的特性是如果當(dāng)前域里面沒有,則會(huì)向上遍歷.一直到最頂層的global scope 比如window.以及,他還可以訪問closure內(nèi)的變量.看demo:
function Auth(username) { var password = "trustno1"; this.eval = function(name) { return eval(name) } // 相當(dāng)于直接this.name } auth = new Auth("Mulder") console.log(auth.eval("username")); // will print "Mulder" console.log(auth.eval("password")); // will print "trustno1"
那有沒有什么辦法可以解決eval這個(gè)特性呢? 答: 沒有. 除非你不用 ok,那我就不用. 我們這里就可以使用new Function(..args,bodyStr) 來(lái)代替eval。
new Functionnew Function就是用來(lái),放回一個(gè)function obj的. 用法參考:new Function. 所以,上面的代碼,放在new Function中,可以寫為: new Function("console.log("a simple script");")();
這樣做在安全性上和eval沒有多大的差別,不過,他不能訪問closure的變量,即通過this來(lái)調(diào)用,而且他的性能比eval要好很多. 那有沒有辦法解決global var的辦法呢? 有啊... 只是有點(diǎn)復(fù)雜先用with,在用Proxy
withwith這個(gè)特性,也算是一個(gè)比較雞肋的,他和eval并列為js兩大SB特性. 不說(shuō)無(wú)用, bug還多,安全性就沒誰(shuí)了... 但是, with的套路總是有人喜歡的.在這里,我們就需要使用到他的特性.因?yàn)?在with的scope里面,所有的變量都會(huì)先從with定義的Obj上查找一遍。
var a = { c:1 } var c =2; with(a){ console.log(c); //等價(jià)于c.a }
所以,第一步改寫上面的new Function(),將里面變量的獲取途徑控制在自己的手里。
function compileCode (src) { src = "with (sandbox) {" + src + "}" return new Function("sandbox", src) }
這樣,所有的內(nèi)容多會(huì)從sandbox這個(gè)str上面獲取,但是找不到的var則又會(huì)向上進(jìn)行搜索. 為了解決這個(gè)問題,則需要使用: proxy
proxyes6 提供的Proxy特性,說(shuō)起來(lái)也是蠻牛逼的. 可以將獲取對(duì)象上的所有方式改寫.具體用法可以參考: 超好用的proxy. 這里,我們只要將has給換掉即可. 有的就好,沒有的就返回undefined
function compileCode (src) { src = "with (sandbox) {" + src + "}" const code = new Function("sandbox", src) return function (sandbox) { const sandboxProxy = new Proxy(sandbox, {has}) return code(sandboxProxy) } } // 相當(dāng)于檢查 獲取的變量是否在里面 like: "in" function has (target, key) { return true } compileCode("log(name)")(console);
這樣的話,就能完美的解決掉 向上查找變量的煩惱了。 另外一些,大神,發(fā)現(xiàn)在新的ECMA里面,有些方法是不會(huì)被with scope 影響的. 這里,主要是通過Symbol.unscopables 這個(gè)特性來(lái)檢測(cè)的.比如:
Object.keys(Array.prototype[Symbol.unscopables]); // ["copyWithin", "entries", "fill", "find", "findIndex", // "includes", "keys", "values"]
不過,經(jīng)過本人測(cè)試發(fā)現(xiàn)也只有Array.prototype上面帶有這個(gè)屬性... 尷尬... 所以,一般而言,我們可以加上 Symbol.unscopables, 也可以不加。
// 還是加一下吧 function compileCode (src) { src = "with (sandbox) {" + src + "}" const code = new Function("sandbox", src) return function (sandbox) { const sandboxProxy = new Proxy(sandbox, {has, get}) return code(sandboxProxy) } } function has (target, key) { return true } function get (target, key) { // 這樣,訪問Array里面的 like, includes之類的方法,就可以保證安全... 算了,就當(dāng)我沒說(shuō),真的沒啥用... if (key === Symbol.unscopables) return undefined return target[key] }
現(xiàn)在,基本上就可以宣告你的代碼是99.999% 的5位安全數(shù).(反正不是100%就行)
設(shè)置緩存如果上代碼,每次編譯一次code時(shí),都會(huì)實(shí)例一次Proxy, 這樣做會(huì)比較損性能. 所以,我們這里,可以使用closure來(lái)進(jìn)行緩存。 上面生成proxy代碼,改寫為:
function compileCode(src) { src = "with (sandbox) {" + src + "}" const code = new Function("sandbox", src) function has(target, key) { return true } function get(target, key) { if (key === Symbol.unscopables) return undefined return target[key] } return (function() { var _sandbox, sandboxProxy; return function(sandbox) { if (sandbox !== _sandbox) { _sandbox = sandbox; sandboxProxy = new Proxy(sandbox, { has, get }) } return code(sandboxProxy) } })() }
不過上面,這樣的緩存機(jī)制有個(gè)弊端,就是不能存儲(chǔ)多個(gè)proxy. 不過,你可以使用Array來(lái)解決,或者更好的使用Map. 這里,我們兩個(gè)都不用,用WeakMap來(lái)解決這個(gè)problem. WeakMap 主要的問題在于,他可以完美的實(shí)現(xiàn),內(nèi)部變量和外部的內(nèi)容的統(tǒng)一. WeakMap最大的特點(diǎn)在于,他存儲(chǔ)的值是不會(huì)被垃圾回收機(jī)制關(guān)注的. 說(shuō)白了, WeakMap引用變量的次數(shù)是不會(huì)算在引用垃圾回收機(jī)制里, 而且, 如果WeakMap存儲(chǔ)的值在外部被垃圾回收裝置回收了,WeakMap里面的值,也會(huì)被刪除--同步效果.所以,毫無(wú)意外, WeakMap是我們最好的一個(gè)tricky. 則,代碼可以寫為:
const sandboxProxies = new WeakMap() function compileCode(src) { src = "with (sandbox) {" + src + "}" const code = new Function("sandbox", src) function has(target, key) { return true } function get(target, key) { if (key === Symbol.unscopables) return undefined return target[key] } return function(sandbox) { if (!sandboxProxies.has(sandbox)) { const sandboxProxy = new Proxy(sandbox, { has, get }) sandboxProxies.set(sandbox, sandboxProxy) } return code(sandboxProxies.get(sandbox)) } }
差不多了, 如果不嫌寫的丑,可以直接拿去用.(如果出事,純屬巧合,本人概不負(fù)責(zé)).
接著,我們來(lái)看一下,如果使用iframe,來(lái)實(shí)現(xiàn)代碼的編譯. 這里,Jsfiddle就是使用這種辦法.
iframe 嵌套最簡(jiǎn)單的方式就是,使用sandbox屬性. 該屬性可以說(shuō)是真正的沙盒... 把sandbox加載iframe里面,那么,你這個(gè)iframe基本上就是個(gè)標(biāo)簽而已... 而且支持性也挺棒的,比如IE10.
這樣已添加,那么下面的事,你都不可以做了:
1. script腳本不能執(zhí)行 2. 不能發(fā)送ajax請(qǐng)求 3. 不能使用本地存儲(chǔ),即localStorage,cookie等 4. 不能創(chuàng)建新的彈窗和window, 比如window.open or target="_blank" 5. 不能發(fā)送表單 6. 不能加載額外插件比如flash等 7. 不能執(zhí)行自動(dòng)播放的tricky. 比如: autofocused, autoplay
看到這里,我也是醉了。 好好的一個(gè)iframe,你這樣是不是有點(diǎn)過分了。 不過,你可以放寬一點(diǎn)權(quán)限。在sandbox里面進(jìn)行一些簡(jiǎn)單設(shè)置
常用的配置項(xiàng)有:
配置 | 效果 |
---|---|
allow-forms | 允許進(jìn)行提交表單 |
allow-scripts | 運(yùn)行執(zhí)行腳本 |
allow-same-origin | 允許同域請(qǐng)求,比如ajax,storage |
allow-top-navigation | 允許iframe能夠主導(dǎo)window.top進(jìn)行頁(yè)面跳轉(zhuǎn) |
allow-popups | 允許iframe中彈出新窗口,比如,window.open,target="_blank" |
allow-pointer-lock | 在iframe中可以鎖定鼠標(biāo),主要和鼠標(biāo)鎖定有關(guān) |
可以通過在sandbox里,添加允許進(jìn)行的權(quán)限.
這樣,就可以保證js腳本的執(zhí)行,但是禁止iframe里的javascript執(zhí)行top.location = self.location。 更多詳細(xì)的內(nèi)容,請(qǐng)參考: please call me HR.
接下來(lái),我們來(lái)具體講解,如果使用iframe來(lái)code evaluation. 里面的原理,還是用到了eval.
iframe 腳本執(zhí)行上面說(shuō)到,我們需要使用eval進(jìn)行方法的執(zhí)行,所以,需要在iframe上面添加上, allow-scripts的屬性.(當(dāng)然,你也可以使用new Function, 這個(gè)隨你...) 這里的框架是使用postMessage+eval. 一個(gè)用來(lái)通信,一個(gè)用來(lái)執(zhí)行. 先看代碼:
Evalbox"s Frame
這里順便插播一下關(guān)于postMessage的相關(guān)知識(shí)點(diǎn).
postMessage 講解postMessage主要做的事情有三個(gè):
1.頁(yè)面和其打開的新窗口的數(shù)據(jù)傳遞 2.多窗口之間消息傳遞 3.頁(yè)面與嵌套的iframe消息傳遞
具體的格式為: otherWindow.postMessage(message, targetOrigin, [transfer]);
message是傳遞的信息,targetOrigin指定的窗口內(nèi)容,transfer取值為Boolean 表示是否可以用來(lái)對(duì)obj進(jìn)行序列化,相當(dāng)于JSON.stringify, 不過一般情況下傳obj時(shí),會(huì)自己先使用JSON進(jìn)行seq一遍. 具體說(shuō)一下targetOrigin. targetOrigin的寫入格式一般為URI,即, protocol+host. 另外,也可以寫為*. 用來(lái)表示 傳到任意的標(biāo)簽頁(yè)中. 另外,就是接受端的參數(shù).接受傳遞的信息,一般是使用window監(jiān)聽message事件.
window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object. if (origin !== "http://example.org:8080") return; // ... }
event里面,會(huì)帶上3個(gè)參數(shù):
data: 傳遞過來(lái)的數(shù)據(jù). e.data
origin: 發(fā)送信息的URL, 比如: https://example.org
source: 發(fā)送信息的源頁(yè)面的window對(duì)象. 我們實(shí)際上只能從上面獲取信息.
該API常常用在window和iframe的信息交流當(dāng)中. 現(xiàn)在,我們回到上面的內(nèi)容.
Evalbox"s Frame
iframe里面,已經(jīng)做好文檔的監(jiān)聽,然后,我們現(xiàn)在需要進(jìn)行內(nèi)容的發(fā)送.直接在index.html寫入:
// html部分 // 設(shè)置基本的安全特性 // js部分 function evaluate() { var frame = document.getElementById("sandboxed"); var code = document.getElementById("code").value; frame.contentWindow.postMessage(code, "/"); // 只想同源的標(biāo)簽頁(yè)發(fā)送 } document.getElementById("safe").addEventListener("click", evaluate); // 同時(shí)設(shè)置接受部分 window.addEventListener("message", function (e) { var frame = document.getElementById("sandboxed"); // 進(jìn)行信息來(lái)源的驗(yàn)證 if (e.origin === "null" && e.source === frame.contentWindow) alert("Result: " + e.data); });
實(shí)際demo可以參考:H5 ROCK
常用的兩種沙箱模式這里差不多講解完了. 開頭說(shuō)了文末有個(gè)彩蛋,這個(gè)彩蛋就是使用nodeJS來(lái)做一下沙箱. 比如像 牛客網(wǎng)的代碼驗(yàn)證,就是放在后端去做代碼的沙箱驗(yàn)證.
彩蛋--nodeJS沙箱使用nodeJS的沙箱很簡(jiǎn)單,就是使用nodeJS提供的VM Module即可. 直接看代碼吧:
const vm = require("vm"); const sandbox = { a: 1, b: 1 }; const script= new vm.Script("a + b"); const context = new vm.createContext(sandbox); script.runInContext(context);
在vm構(gòu)建出來(lái)的sandbox里面,沒有任何可以訪問的全局變量.除了基本的syntax.
原文鏈接:http://www.ivweb.io/topic/58d...
此文已由騰訊云+社區(qū)在各渠道發(fā)布
獲取更多新鮮技術(shù)干貨,可以關(guān)注我們騰訊云技術(shù)社區(qū)-云加社區(qū)官方號(hào)及知乎機(jī)構(gòu)號(hào)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/73580.html
摘要:本文由云社區(qū)發(fā)表作者市面上現(xiàn)在流行兩種沙箱模式一種是使用還有一種是直接在頁(yè)面上使用進(jìn)行執(zhí)行。接下來(lái)我們來(lái)一步一步分析如果做到在前端的沙箱文末看俺有沒有心情放一個(gè)彩蛋吧。等價(jià)于所以第一步改寫上面的將里面變量的獲取途徑控制在自己的手里。 本文由云+社區(qū)發(fā)表作者:ivweb villainthr 市面上現(xiàn)在流行兩種沙箱模式,一種是使用iframe,還有一種是直接在頁(yè)面上使用new Func...
摘要:沙箱,第一反應(yīng),但并不是。為避免誤解,下文中所有提到都是指這個(gè)工具,不是沙箱。做什么的,根據(jù)其官網(wǎng)上的描述可以理解為快速生成,即時(shí)部署,協(xié)作構(gòu)建并集成了調(diào)試工具。對(duì)個(gè)人而言,強(qiáng)烈推薦給入門的人。 Sandbox--沙箱,第一反應(yīng),但sorry并不是。 sandbox 這個(gè)詞大家并不陌生,但貌似國(guó)內(nèi)的技術(shù)share中很少看到有提到這個(gè)工具的。為避免誤解,下文中所有提到sandbox都是指...
摘要:好好的一個(gè),你這樣是不是有點(diǎn)過分了。不過,你可以放寬一點(diǎn)權(quán)限。 摘至: please call me HR 市面上現(xiàn)在流行兩種沙箱模式,一種是使用iframe,還有一種是直接在頁(yè)面上使用new Function + eval進(jìn)行執(zhí)行. 殊途同歸,主要還是防止一些Hacker們 吃飽了沒事干,收別人錢來(lái) Hack 你的網(wǎng)站. 一般情況, 我們的代碼量有60%業(yè)務(wù)+40%安全. 剩下的就看...
摘要:而標(biāo)準(zhǔn)庫(kù)中的是不安全的,用戶腳本可以輕易突破沙箱環(huán)境,獲取主程序的上述代碼在執(zhí)行時(shí),程序在第二行就直接退出,虛擬機(jī)環(huán)境中的代碼逃逸,獲得了主線程的變量,并調(diào)用,造成主程序非正常退出。 NPM酷庫(kù),每天兩分鐘,了解一個(gè)流行NPM庫(kù)。 今天我們要了解的庫(kù)是 vm2,則是一個(gè)Node.js 官方 vm 庫(kù)的替代品,主要解決了安全問題。 不安全的vm 在Node.js官方標(biāo)準(zhǔn)庫(kù)中有一個(gè)vm庫(kù),...
摘要:當(dāng)運(yùn)行函數(shù)的時(shí)候,只能訪問自己的本地變量和全局變量,不能訪問構(gòu)造器被調(diào)用生成的上下文的作用域。如何建立一個(gè)更安全一些的沙箱通過上文的探究,我們并沒有找到一個(gè)完美的方案在建立安全的隔離的沙箱。 showImg(https://segmentfault.com/img/remote/1460000014575992); 有哪些動(dòng)態(tài)執(zhí)行腳本的場(chǎng)景? 在一些應(yīng)用中,我們希望給用戶提供插入自定義...
閱讀 2019·2023-04-26 02:15
閱讀 2302·2021-11-19 09:40
閱讀 1038·2021-10-27 14:13
閱讀 3307·2021-08-23 09:44
閱讀 3609·2019-12-27 12:24
閱讀 652·2019-08-30 15:53
閱讀 1164·2019-08-30 10:53
閱讀 2153·2019-08-26 12:14