偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

淺析JavaScript的沙箱內(nèi)容

開(kāi)發(fā) 前端
市面上現(xiàn)在流行兩種沙箱模式,一種是使用iframe,還有一種是直接在頁(yè)面上使用new Function + eval進(jìn)行執(zhí)行. 殊途同歸,主要還是防止一些Hacker們 吃飽了沒(méi)事干,收別人錢來(lái) Hack 你的網(wǎng)站. 一般情況, 我們的代碼量有60%業(yè)務(wù)+40%安全. 剩下的就看天意了.接下來(lái),我們來(lái)一步一步分析,如果做到在前端的沙箱.文末 看俺有沒(méi)有心情放一個(gè)彩蛋吧.

[[171101]]

市面上現(xiàn)在流行兩種沙箱模式,一種是使用iframe,還有一種是直接在頁(yè)面上使用new Function + eval進(jìn)行執(zhí)行. 殊途同歸,主要還是防止一些Hacker們 吃飽了沒(méi)事干,收別人錢來(lái) Hack 你的網(wǎng)站. 一般情況, 我們的代碼量有60%業(yè)務(wù)+40%安全. 剩下的就看天意了.

接下來(lái),我們來(lái)一步一步分析,如果做到在前端的沙箱.文末 看俺有沒(méi)有心情放一個(gè)彩蛋吧.

直接嵌套

這種方式說(shuō)起來(lái)并不是什么特別好的點(diǎn)子,因?yàn)樾枰ㄙM(fèi)比較多的精力在安全性上.

eval執(zhí)行

最簡(jiǎn)單的方式,就是使用eval進(jìn)行代碼的執(zhí)行

  1. eval('console.log("a simple script");'); 

但,如果你是直接這么使用的話, congraduations... do die...

因?yàn)?eval 的特性是如果當(dāng)前域里面沒(méi)有,則會(huì)向上遍歷.一直到最頂層的global scope 比如window.以及,他還可以訪問(wèn)closure內(nèi)的變量.看demo:

  1. function Auth(username) 
  2.   var password = "trustno1"
  3.   this.eval = function(name) { return eval(name) } // 相當(dāng)于直接this.name 
  4.  
  5. auth = new Auth("Mulder"
  6. console.log(auth.eval("username")); // will print "Mulder" 
  7. console.log(auth.eval("password")); // will print "trustno1"  

那有沒(méi)有什么辦法可以解決eval這個(gè)特性呢?

答: 沒(méi)有. 除非你不用

ok,那我就不用. 我們這里就可以使用new Function(..args,bodyStr) 來(lái)代替eval.

new Function

new Function就是用來(lái),放回一個(gè)function obj的. 用法參考:new Function.

所以,上面的代碼,放在new Function中,可以寫為:

  1. new Function('console.log("a simple script");')(); 

這樣做在安全性上和eval沒(méi)有多大的差別,不過(guò),他不能訪問(wèn)closure的變量,即通過(guò)this來(lái)調(diào)用,而且他的性能比eval要好很多. 那有沒(méi)有辦法解決global var的辦法呢?

有啊... 只是有點(diǎn)復(fù)雜先用with,在用Proxy

with

with這個(gè)特性,也算是一個(gè)比較雞肋的,他和eval并列為js兩大SB特性. 不說(shuō)無(wú)用, bug還多,安全性就沒(méi)誰(shuí)了... 但是, with的套路總是有人喜歡的.在這里,我們就需要使用到他的特性.因?yàn)?在with的scope里面,所有的變量都會(huì)先從with定義的Obj上查找一遍.

  1. var a = { 
  2.     c:1 
  3. var c =2; 
  4. with(a){ 
  5.     console.log(c); //等價(jià)于c.a 
  6.  

所以,第一步改寫上面的new Function(),將里面變量的獲取途徑控制在自己的手里.

  1. function compileCode (src) {   
  2.   src = 'with (sandbox) {' + src + '}' 
  3.   return new Function('sandbox', src) 
  4.  

這樣,所有的內(nèi)容多會(huì)從sandbox這個(gè)str上面獲取,但是找不到的var則又會(huì)向上進(jìn)行搜索. 為了解決這個(gè)問(wèn)題,則需要使用: proxy

proxy

es6 提供的Proxy特性,說(shuō)起來(lái)也是蠻牛逼的. 可以將獲取對(duì)象上的所有方式改寫.具體用法可以參考: 超好用的proxy.

這里,我們只要將has給換掉即可. 有的就好,沒(méi)有的就返回undefined

  1. function compileCode (src) { 
  2.   src = 'with (sandbox) {' + src + '}' 
  3.   const code = new Function('sandbox', src) 
  4.  
  5.   return function (sandbox) { 
  6.     const sandboxProxy = new Proxy(sandbox, {has}) 
  7.     return code(sandboxProxy) 
  8.   } 
  9.  
  10. // 相當(dāng)于檢查 獲取的變量是否在里面 like'in' 
  11. function has (target, key) { 
  12.   return true 
  13.  
  14. compileCode('log(name)')(console);  

// 相當(dāng)于檢查 獲取的變量是否在里面 like: 'in'

  1. Object.keys(Array.prototype[Symbol.unscopables]);  
  2. // ["copyWithin""entries""fill""find""findIndex",  
  3. //  "includes""keys""values" 

這樣的話,就能完美的解決掉 向上查找變量的煩惱了. 另外一些大神,發(fā)現(xiàn)在新的ECMA里面,有些方法是不會(huì)被with scope 影響的. 這里,主要是通過(guò)Symbol.unscopables 這個(gè)特性來(lái)檢測(cè)的.比如:

  1. // 還是加一下吧 
  2. function compileCode (src) {   
  3.   src = 'with (sandbox) {' + src + '}' 
  4.   const code = new Function('sandbox', src) 
  5.  
  6.   return function (sandbox) { 
  7.     const sandboxProxy = new Proxy(sandbox, {has, get}) 
  8.     return code(sandboxProxy) 
  9.   } 
  10.  
  11. function has (target, key) {   
  12.   return true 
  13.  
  14. function get (target, key) {   
  15. // 這樣,訪問(wèn)Array里面的 like, includes之類的方法,就可以保證安全... 算了,就當(dāng)我沒(méi)說(shuō),真的沒(méi)啥用... 
  16.   if (key === Symbol.unscopables) return undefined 
  17.   return target[key
  18.  

現(xiàn)在,基本上就可以宣告你的代碼是99.999% 的5位安全數(shù).(反正不是100%就行)

設(shè)置緩存

如果上代碼,每次編譯一次code時(shí),都會(huì)實(shí)例一次Proxy, 這樣做會(huì)比較損性能. 所以,我們這里,可以使用closure來(lái)進(jìn)行緩存。 上面生成proxy代碼,改寫為:

  1. function compileCode(src) { 
  2.     src = 'with (sandbox) {' + src + '}' 
  3.     const code = new Function('sandbox', src) 
  4.  
  5.     function has(target, key) { 
  6.         return true 
  7.     } 
  8.  
  9.     function get(target, key) { 
  10.         if (key === Symbol.unscopables) return undefined 
  11.         return target[key
  12.     } 
  13.  
  14.     return (function() { 
  15.         var _sandbox, sandboxProxy; 
  16.         return function(sandbox) { 
  17.             if (sandbox !== _sandbox) { 
  18.                 _sandbox = sandbox; 
  19.                 sandboxProxy = new Proxy(sandbox, { has, get }) 
  20.             } 
  21.             return code(sandboxProxy) 
  22.         } 
  23.     })() 
  24.  

不過(guò)上面,這樣的緩存機(jī)制有個(gè)弊端,就是不能存儲(chǔ)多個(gè)proxy. 不過(guò),你可以使用Array來(lái)解決,或者更好的使用Map. 這里,我們兩個(gè)都不用,用WeakMap來(lái)解決這個(gè)problem. WeakMap 主要的問(wèn)題在于,他可以完美的實(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. 則,代碼可以寫為:

  1. const sandboxProxies = new WeakMap() 
  2. function compileCode(src) { 
  3.     src = 'with (sandbox) {' + src + '}' 
  4.     const code = new Function('sandbox', src) 
  5.  
  6.     function has(target, key) { 
  7.         return true 
  8.     } 
  9.  
  10.     function get(target, key) { 
  11.         if (key === Symbol.unscopables) return undefined 
  12.         return target[key
  13.     } 
  14.     return function(sandbox) { 
  15.         if (!sandboxProxies.has(sandbox)) { 
  16.             const sandboxProxy = new Proxy(sandbox, { has, get }) 
  17.             sandboxProxies.set(sandbox, sandboxProxy) 
  18.         } 
  19.         return code(sandboxProxies.get(sandbox)) 
  20.     } 
  21.  

差不多了, 如果不嫌寫的丑,可以直接拿去用.(如果出事,純屬巧合,本人概不負(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. <iframe sandbox src=”...”></iframe> 

這樣已添加,那么下面的事,你都不可以做了:

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)過(guò)分了。 不過(guò),你可以放寬一點(diǎn)權(quán)限。在sandbox里面進(jìn)行一些簡(jiǎn)單設(shè)置

  1. <iframe sandbox=”allow-same-origin” src=”...”></iframe> 

常用的配置項(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)

可以通過(guò)在sandbox里,添加允許進(jìn)行的權(quán)限.

  1. <iframe sandbox=”allow-forms allow-same-origin allow-scripts” src=”...”></iframe> 

這樣,就可以保證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í)行.

先看代碼:

  1. <!-- frame.html --> 
  2. <!DOCTYPE html> 
  3. <html> 
  4.  <head> 
  5.    <title>Evalbox's Frame</title> 
  6.    <script> 
  7.      window.addEventListener('message'function (e) { 
  8.      // 相當(dāng)于window.top.currentWindow. 
  9.        var mainWindow = e.source; 
  10.        var result = ''
  11.        try { 
  12.          result = eval(e.data); 
  13.        } catch (e) { 
  14.          result = 'eval() threw an exception.'
  15.        } 
  16.        // e.origin 就是原來(lái)window的url 
  17.        mainWindow.postMessage(result, e.origin); 
  18.      }); 
  19.    </script> 
  20.  </head> 
  21. </html>  

這里順便插播一下關(guān)于postMessage的相關(guān)知識(shí)點(diǎn).

postMessage 講解

postMessage主要做的事情有三個(gè):

1.頁(yè)面和其打開(kāi)的新窗口的數(shù)據(jù)傳遞

2.多窗口之間消息傳遞

3.頁(yè)面與嵌套的iframe消息傳遞

具體的格式為 

  1. otherWindow.postMessage(message, targetOrigin, [transfer]); 

message是傳遞的信息,targetOrigin指定的窗口內(nèi)容,transfer取值為Boolean 表示是否可以用來(lái)對(duì)obj進(jìn)行序列化,相當(dāng)于JSON.stringify, 不過(guò)一般情況下傳obj時(shí),會(huì)自己先使用JSON進(jìn)行seq一遍.

具體說(shuō)一下targetOrigin.

targetOrigin的寫入格式一般為URI,即, protocol+host. 另外,也可以寫為*. 用來(lái)表示 傳到任意的標(biāo)簽頁(yè)中.

另外,就是接受端的參數(shù).接受傳遞的信息,一般是使用window監(jiān)聽(tīng)message事件.

  1. window.addEventListener("message", receiveMessage, false); 
  2.  
  3. function receiveMessage(event) 
  4.   var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object. 
  5.   if (origin !== "http://example.org:8080"
  6.     return
  7.  
  8.   // ... 
  9.  

event里面,會(huì)帶上3個(gè)參數(shù):

  • data: 傳遞過(guò)來(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)容.

  1. <!-- frame.html --> 
  2. <!DOCTYPE html> 
  3. <html> 
  4.  <head> 
  5.    <title>Evalbox's Frame</title> 
  6.    <script> 
  7.      window.addEventListener('message'function (e) { 
  8.      // 相當(dāng)于window.top.currentWindow. 
  9.        var mainWindow = e.source; 
  10.        var result = ''
  11.        try { 
  12.          result = eval(e.data); 
  13.        } catch (e) { 
  14.          result = 'eval() threw an exception.'
  15.        } 
  16.        // e.origin 就是原來(lái)window的url 
  17.        mainWindow.postMessage(result, e.origin); 
  18.      }); 
  19.    </script> 
  20.  </head> 
  21. </html>  

iframe里面,已經(jīng)做好文檔的監(jiān)聽(tīng),然后,我們現(xiàn)在需要進(jìn)行內(nèi)容的發(fā)送.直接在index.html寫入:

  1. // html部分 
  2. <textarea id='code'></textarea> 
  3. <button id='safe'>eval() in a sandboxed frame.</button> 
  4. // 設(shè)置基本的安全特性 
  5. <iframe sandbox='allow-scripts' 
  6.         id='sandboxed' 
  7.         src='frame.html'></iframe> 
  8.  
  9. // js部分 
  10. function evaluate() { 
  11.   var frame = document.getElementById('sandboxed'); 
  12.   var code = document.getElementById('code').value; 
  13.   frame.contentWindow.postMessage(code, '/'); // 只想同源的標(biāo)簽頁(yè)發(fā)送 
  14.  
  15. document.getElementById('safe').addEventListener('click', evaluate); 
  16.  
  17. // 同時(shí)設(shè)置接受部分 
  18. window.addEventListener('message'
  19.     function (e) { 
  20.       var frame = document.getElementById('sandboxed'); 
  21.       // 進(jìn)行信息來(lái)源的驗(yàn)證 
  22.       if (e.origin === "null" && e.source === frame.contentWindow) 
  23.         alert('Result: ' + e.data); 
  24.     });  

實(shí)際demo可以參考:H5 ROCK

常用的兩種沙箱模式這里差不多講解完了. 開(kāi)頭說(shuō)了文末有個(gè)彩蛋,這個(gè)彩蛋就是使用nodeJS來(lái)做一下沙箱. 比如像 牛客網(wǎng)的代碼驗(yàn)證,就是放在后端去做代碼的沙箱驗(yàn)證.

彩蛋--nodeJS沙箱

使用nodeJS的沙箱很簡(jiǎn)單,就是使用nodeJS提供的VM Module即可.

直接看代碼吧:

  1. const vm = require('vm'); 
  2. const sandbox = { a: 1, b: 1 }; 
  3. const script = new vm.Script('a + b'); 
  4. const context = new vm.createContext(sandbox); 
  5. script.runInContext(context);  

在vm構(gòu)建出來(lái)的sandbox里面,沒(méi)有任何可以訪問(wèn)的全局變量.除了基本的syntax.

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2021-10-25 10:30:12

JavaScript開(kāi)發(fā) 代碼

2021-07-27 22:56:00

JavaScript編程開(kāi)發(fā)

2011-03-07 09:41:10

JavaScript

2021-02-07 22:59:55

JavaScript編程方法鏈

2009-07-24 17:30:37

Javascript閉

2016-10-19 14:35:20

JavaScript函數(shù)式編程

2011-03-10 14:19:56

JavaScript

2024-12-23 15:54:51

2010-09-28 14:12:50

Javascript

2023-11-12 21:37:56

Feed流

2021-04-09 08:51:32

Web WorkerJavaScript微前端

2009-07-15 16:03:26

Swing線程

2016-09-14 21:28:25

JavaScript事件代理委托

2011-03-08 09:15:04

JavaScript

2022-01-17 21:37:24

JavaScriptHTMLCSS

2009-07-14 11:34:42

MyEclipse斷點(diǎn)JavaScript

2009-09-14 19:21:36

Javascript透

2009-10-12 10:33:11

Javascript替

2021-03-16 10:00:40

JavaScript用戶登錄表單JavaScript基

2009-09-16 16:32:20

JavaScript靜
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)