真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Node.js中怎么設(shè)置沙箱環(huán)境

今天就跟大家聊聊有關(guān)Node.js中怎么設(shè)置沙箱環(huán)境,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

創(chuàng)新互聯(lián)建站是專業(yè)的榕江網(wǎng)站建設(shè)公司,榕江接單;提供網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行榕江網(wǎng)站開發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!

怎樣安全的執(zhí)行動(dòng)態(tài)腳本?

我們先看看通常都能如何在 JavaScript 程序中動(dòng)態(tài)執(zhí)行一段代碼?比如大名頂頂?shù)?eval

eval('1+2')

上述代碼沒有問題順利執(zhí)行了,eval 是全局對(duì)象的一個(gè)函數(shù)屬性,執(zhí)行的代碼擁有著和應(yīng)程中其它正常代碼一樣的的權(quán)限,它能訪問「執(zhí)行上下文」中的局部變量,也能訪問所有「全局變量」,在這個(gè)場(chǎng)景下,它是一個(gè)非常危險(xiǎn)的函數(shù)。

再來看看 Functon,通過 Function 構(gòu)造器,我們可以動(dòng)態(tài)的創(chuàng)建一個(gè)函數(shù),然后執(zhí)行它

const sum = new Function('m', 'n', 'return m + n');
console.log(sum(1, 2));

它也一樣的順利執(zhí)行了,使用 Function 構(gòu)造器生成的函數(shù),并不會(huì)在創(chuàng)建它的上下文中創(chuàng)建閉包,一般在全局作用域中被創(chuàng)建。當(dāng)運(yùn)行函數(shù)的時(shí)候,只能訪問自己的本地變量和全局變量,不能訪問 Function 構(gòu)造器被調(diào)用生成的上下文的作用域。如同一個(gè)站在地上、一個(gè)站在一張薄薄的紙上一樣,在這個(gè)場(chǎng)景下,幾乎沒有高下之分。

結(jié)合 ES6 的新特性 Proxy 便能更安全一些

function evalute(code,sandbox) {
 sandbox = sandbox || Object.create(null);
 const fn = new Function('sandbox', `with(sandbox){return ($[code])}`);
 const proxy = new Proxy(sandbox, {
 has(target, key) {
  // 讓動(dòng)態(tài)執(zhí)行的代碼認(rèn)為屬性已存在
  return true; 
 }
 });
 return fn(proxy);
}
evalute('1+2') // 3
evalute('console.log(1)') // Cannot read property 'log' of undefined

我們知道無論 eval 還是 function,執(zhí)行時(shí)都會(huì)把作用域一層一層向上查找,如果找不到會(huì)一直到 global,那么利用 Proxy 的原理就是,讓執(zhí)行了代碼在 sandobx 中找的到,以達(dá)到「防逃逸」的目的。

在瀏覽器中,還可以利用 iframe,創(chuàng)建一個(gè)再發(fā)安全的一些隔離環(huán)境,本文也著眼于 Node.js,在這里不做過多討論。
在 Node.js 中呢,有沒有其它選擇?

或許沒看到這兒之前你就已經(jīng)想到了 VM,它是 Node.js 默認(rèn)就提供的一個(gè)內(nèi)建模塊,VM 模塊提供了一系列 API 用于在 V8 虛擬機(jī)環(huán)境中編譯和運(yùn)行代碼。JavaScript 代碼可以被編譯并立即運(yùn)行,或編譯、保存然后再運(yùn)行。

const vm = require('vm');
const script = new vm.Script('m + n');
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
script.runInContext(context);

執(zhí)行上這的代碼就能拿到結(jié)果 3,同時(shí),通過 vm.Script 還能指定代碼執(zhí)行了「最大毫秒數(shù)」,超過指定的時(shí)長(zhǎng)將終止執(zhí)行并拋出一個(gè)異常

try {
 const script = new vm.Script('while(true){}',{ timeout: 50 });
 ....
} catch (err){
 //打印超時(shí)的 log
 console.log(err.message);
}

上面的腳本執(zhí)行將會(huì)失敗,被檢測(cè)到超時(shí)并拋出異常,然后被 Try Cache 捕獲到并打出 log,但同時(shí)需要注意的是 vm.Script 的 timeout 選項(xiàng)「只針對(duì)同步代有效」,而不包括是異步調(diào)用的時(shí)間,比如

const script = new vm.Script('setTimeout(()=>{},2000)',{ timeout: 50 });
 ....

上述代碼,并不是會(huì)在 50ms 后拋出異常,因?yàn)?50ms 上邊的代碼同步執(zhí)行肯定完了,而 setTimeout 所用的時(shí)間并不算在內(nèi),也就是說 vm 模塊沒有辦法對(duì)異步代碼直接限制執(zhí)行時(shí)間。我們也不能額外通過一個(gè) timer 去檢查超時(shí),因?yàn)闄z查了執(zhí)行中的 vm 也沒有方法去中止掉。

另外,在 Node.js 通過 vm.runInContext 看起來似乎隔離了代碼執(zhí)行環(huán)境,但實(shí)際上卻很容易「逃逸」出去。

const vm = require('vm');
const sandbox = {};
const script = new vm.Script('this.constructor.constructor("return process")().exit()');
const context = vm.createContext(sandbox);
script.runInContext(context);

執(zhí)行上邊的代碼,宿主程序立即就會(huì)「退出」,sandbox 是在 VM 之外的環(huán)境創(chuàng)建的,需 VM 中的代碼的 this 指向的也是 sandbox,那么

//this.constructor 就是外所的 Object 構(gòu)建函數(shù)
const ObjConstructor = this.constructor; 
//ObjConstructor 的 constructor 就是外包的 Function
const Function = ObjConstructor.constructor;
//創(chuàng)建一個(gè)函數(shù),并執(zhí)行它,返回全局 process 全局對(duì)象
const process = (new Function('return process'))(); 
//退出當(dāng)前進(jìn)程
process.exit();

沒有人愿意用戶一段腳本就能讓應(yīng)用掛掉吧。除了退出進(jìn)程序之外,實(shí)際上還能干更多的事情。

有個(gè)簡(jiǎn)單的方法就能避免通過 this.constructor 拿到 process,如下:

const vm = require('vm');
//創(chuàng)建一外無 proto 的空白對(duì)象作為 sandbox
const sandbox = Object.create(null);
const script = new vm.Script('...');
const context = vm.createContext(sandbox);
script.runInContext(context);

但還是有風(fēng)險(xiǎn)的,由于 JavaScript 本身的動(dòng)態(tài)的特點(diǎn),各種黑魔法防不勝防。事實(shí) Node.js 的官方文檔中也提到 VM 當(dāng)做一個(gè)安全的沙箱去執(zhí)行任意非信任的代碼。

有哪些做了進(jìn)一步工作的社區(qū)模塊?

在社區(qū)中有一些開源的模塊用于運(yùn)行不信任代碼,例如 sandbox、vm2、jailed 等。相比較而言 vm2 對(duì)各方面做了更多的安全工作,相對(duì)安全些。

從 vm2 的官方 READM 中可以看到,它基于 Node.js 內(nèi)建的 VM 模塊,來建立基礎(chǔ)的沙箱環(huán)境,然后同時(shí)使用上了文介紹過的 ES6 的 Proxy 技術(shù)來防止沙箱腳本逃逸。

用同樣的測(cè)試代碼來試試 vm2

const { VM } = require('vm2');
new VM().run('this.constructor.constructor("return process")().exit()');

如上代碼,并沒有成功結(jié)束掉宿主程序,vm2 官方 REAME 中說「vm2 是一個(gè)沙盒,可以在 Node.js 中按全的執(zhí)行不受信任的代碼」。

然而,事實(shí)上我們還是可以干一些「壞」事情,比如:

const { VM } = require('vm2');
const vm = new VM({ timeout: 1000, sandbox: {}});
vm.run('new Promise(()=>{})');

上邊的代碼將永遠(yuǎn)不會(huì)執(zhí)行結(jié)束,如同 Node.js 內(nèi)建模塊一樣 vm2 的 timeout 對(duì)異步操作是無效的。同時(shí),vm2 也不能額外通過一個(gè) timer 去檢查超時(shí),因?yàn)樗矝]有辦法將執(zhí)行中的 vm 終止掉。這會(huì)一點(diǎn)點(diǎn)耗費(fèi)完服務(wù)器的資源,讓你的應(yīng)用掛掉。

那么或許你會(huì)想,我們能不能在上邊的 sandbox 中放一個(gè)假的 Promise 從而禁掉 Promise 呢?答案是能提供一個(gè)「假」的 Promise,但卻沒有辦法完成禁掉 Promise,比如

const { VM } = require('vm2');
const vm = new VM({ 
 timeout: 1000, sandbox: { Promise: function(){}}
});
vm.run('Promise = (async function(){})().constructor;new Promise(()=>{});');

可以看到通過一行 Promise = (async function(){})().constructor 就可以輕松再次拿到 Promise 了。從另一個(gè)層面來看,況且或許有時(shí)我們還想讓自定義腳本支持異步處理呢。

如何建立一個(gè)更安全一些的沙箱?

通過上文的探究,我們并沒有找到一個(gè)完美的方案在 Node.js 建立安全的隔離的沙箱。其中 vm2 做了不少處理,相對(duì)來講算是較安全的方案了,但問題也很明顯,比如異步不能檢查超時(shí)的問題、和宿主程序在相同進(jìn)程的問題。

沒有進(jìn)程隔離時(shí),通過 VM 創(chuàng)建的 sanbox 大體是這樣的

Node.js中怎么設(shè)置沙箱環(huán)境

那么,我們是不是可以嘗試,將非受信代碼,通過 vm2 這個(gè)模塊隔離在一個(gè)獨(dú)立的進(jìn)程中執(zhí)行呢?然后,執(zhí)行超時(shí)時(shí),直接將隔離的進(jìn)程干掉,但這里我們需要考慮如下幾個(gè)問題

通過進(jìn)程池統(tǒng)調(diào)度管理沙箱進(jìn)程

如果來一個(gè)執(zhí)行任務(wù),創(chuàng)建一個(gè)進(jìn)程,用完銷毀,僅處理進(jìn)程的開銷就已經(jīng)稍大了,并且也不能不設(shè)限的開新進(jìn)程和宿主應(yīng)用搶資源,那么,需要建一個(gè)進(jìn)程池,所有任務(wù)到來會(huì)創(chuàng)建一個(gè) Script 實(shí)例,先進(jìn)入一個(gè) pending 隊(duì)列,然后直接將 script 實(shí)例的 defer 對(duì)象返回,調(diào)用處就能 await 執(zhí)行結(jié)果了,然后由 sandbox master 根據(jù)工程進(jìn)程的空閑程序來調(diào)度執(zhí)行,master 會(huì)將 script 的執(zhí)行信息,包括重要的 ScriptId,發(fā)送給空閑的 worker,worker 執(zhí)行完成后會(huì)將「結(jié)果 + script 信息」回傳給 master,master 通過 ScriptId 識(shí)別是哪個(gè)腳本執(zhí)行完畢了,就是結(jié)果進(jìn)行 resolve 或 reject 處理。

這樣,通過「進(jìn)程池」即能降低「進(jìn)程來回創(chuàng)建和銷毀的開銷」,也能確保不過度搶占宿主資源,同時(shí),在異步操作超時(shí),還能將工程進(jìn)程直接殺掉,同時(shí),master 將發(fā)現(xiàn)一個(gè)工程進(jìn)程掛掉,會(huì)立即創(chuàng)建替補(bǔ)進(jìn)程。

處理的數(shù)據(jù)和結(jié)果,還有公開給沙箱的方法

進(jìn)程間如何通訊,需要「動(dòng)態(tài)代碼」處理數(shù)據(jù)可以直接序列化后通過 IPC 發(fā)送給隔離 Sandbox 進(jìn)程,執(zhí)行結(jié)果一樣經(jīng)過序列化通過 IPC 傳輸。

其中,如果想法公開一個(gè)方法給 sandbox,因?yàn)椴辉谝粋€(gè)進(jìn)程,并不能方便的將一個(gè)方案的引用傳遞給 sandbox。我們可以將宿主的方法,在傳遞給 sandbox worker 之類做一下處理,轉(zhuǎn)換為一個(gè)「描述對(duì)象」,包括了允許 sandbox 調(diào)用的方法信息,然后將信息,如同其它數(shù)據(jù)一樣發(fā)送給 worker 進(jìn)程,worker 收到數(shù)據(jù)后,識(shí)出來所「方法描述對(duì)象」,然后在 worker 進(jìn)程中的 sandbox 對(duì)象上建立代理方法,代理方法同樣通過 IPC 和 master 通訊。

最終,我們建立了一個(gè)大約這樣的「沙箱環(huán)境」

Node.js中怎么設(shè)置沙箱環(huán)境

如此這般處理起來是不是感覺很麻煩?但我們就有了一個(gè)更加安全一些的沙箱環(huán)境了,這些處理。筆者已經(jīng)基于 TypeScript 編寫,并封裝為一個(gè)獨(dú)立的模塊 Safeify。

GitHub: https://github.com/Houfeng/safeify,歡迎 Star & Issues

最后,簡(jiǎn)單介紹一下 Safeify 如何使用,通過如下命令安裝

npm i safeify --save

在應(yīng)用中使用,還是比較簡(jiǎn)單的,如下代碼(TypeScript 中類似)

import { Safeify } from './Safeify';

const safeVm = new Safeify({
 timeout: 50,     //超時(shí)時(shí)間,默認(rèn) 50ms
 asyncTimeout: 500,  //包含異步操作的超時(shí)時(shí)間,默認(rèn) 500ms
 quantity: 4      //沙箱進(jìn)程數(shù)量,默認(rèn)同 CPU 核數(shù)
});

const context = {
 a: 1, 
 b: 2,
 add(a, b) {
  return a + b;
 }
};

const rs = await safeVm.run(`return add(a,b)`, context);
console.log('result',rs);

看完上述內(nèi)容,你們對(duì)Node.js中怎么設(shè)置沙箱環(huán)境有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。


文章題目:Node.js中怎么設(shè)置沙箱環(huán)境
文章URL:http://weahome.cn/article/iphehd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部