摘要:下面我們介紹提供的模塊以及分析用它來(lái)運(yùn)行不信任代碼可能遇到的問(wèn)題。模塊模塊是內(nèi)置的核心模塊,它能讓我們編譯代碼和在指定的環(huán)境中運(yùn)行。上有一些開源的模塊用于運(yùn)行不信任代碼,例如,,等。
在一些系統(tǒng)中,我們希望給用戶提供插入自定義邏輯的能力,除了 RPC 和 REST 之外,運(yùn)行客戶提供的代碼也是比較常用的方法,好處是可以極大地減少在網(wǎng)絡(luò)上的耗時(shí)。JavaScript 是一種非常流行而且容易上手的語(yǔ)言,因此,讓用戶用 JavaScript 來(lái)寫自定義邏輯是一個(gè)不錯(cuò)的選擇。下面我們介紹 Node.js 提供的 vm 模塊以及分析用它來(lái)運(yùn)行不信任代碼可能遇到的問(wèn)題。
vm 模塊vm 模塊是 Node.js 內(nèi)置的核心模塊,它能讓我們編譯 JavaScript 代碼和在指定的環(huán)境中運(yùn)行。請(qǐng)看下面例子:
const util = require("util"); const vm = require("vm"); // 1. 創(chuàng)建一個(gè) vm.Script 實(shí)例, 編譯要執(zhí)行的代碼 const script = new vm.Script("globalVar += 1; anotherGlobalVar = 1; "); // 2. 用于綁定到 context 的對(duì)象 const sandbox = {globalVar: 1}; // 3. 創(chuàng)建一個(gè) context, 并且把 sandbox 這個(gè)對(duì)象綁定到這個(gè)環(huán)境, 作為全局對(duì)象 const contextifiedSandbox = vm.createContext(sandbox); // 4. 運(yùn)行上面編譯的代碼, context 是 contextifiedSandbox const result = script.runInContext(contextifiedSandbox); console.log(`sandbox === contextifiedSandbox ? ${sandbox === contextifiedSandbox}`); // sandbox === contextifiedSandbox ? true console.log(`sandbox: ${util.inspect(sandbox)}`); // sandbox: { globalVar: 2, anotherGlobalVar: 1 } console.log(`result: ${util.inspect(result)}`); // result: 1
vm.Script 是一個(gè)類,用于創(chuàng)建代碼實(shí)例,后面可以多次運(yùn)行。
vm.createContext(sandbox) 用于 "contextify" 一個(gè)對(duì)象,根據(jù) ECMAScript 2015 語(yǔ)言規(guī)范,代碼的執(zhí)行需要一個(gè) execution context。這里的 "contextify",就是把傳進(jìn)去的對(duì)象與 V8 的一個(gè)新的 context 進(jìn)行關(guān)聯(lián)。這里所說(shuō)的關(guān)聯(lián),我的理解是,這個(gè) "contextified" 對(duì)象的屬性將會(huì)成為那個(gè) context 的全局屬性,同時(shí),在 context 下運(yùn)行代碼時(shí)產(chǎn)生的全局屬性也會(huì)成為這個(gè) "contextified" 對(duì)象的屬性。
script.runInContext(contextifiedSandbox) 就是使代碼在 contextifiedSandbox 這個(gè) context 中運(yùn)行,從上面的輸出可以看到,代碼運(yùn)行后,contextifiedSandbox 里面的屬性的值已經(jīng)被改變了,運(yùn)行結(jié)果是最后一個(gè)表達(dá)式的值。
除了上面幾個(gè)接口之外,vm 模塊還有一些更便捷的接口,例如 vm.runInContext(code, contextifiedSandbox[, options]),vm.runInNewContext(code[, sandbox][, options])等,詳細(xì)可看文檔。
外層如何得到代碼運(yùn)行結(jié)果我們用 vm 運(yùn)行代碼的時(shí)候很可能需要得到一些結(jié)果,從上面的例子中可以看到,我們可以通過(guò)把結(jié)果作為最后一個(gè)表達(dá)式的值傳給外層,或者作為context 的屬性給外層使用,這在同步代碼里沒有問(wèn)題,但是假如結(jié)果需要依賴?yán)锩娴漠惒讲僮髂??這時(shí),我們可以通過(guò)在 context 里放一個(gè)回調(diào)函數(shù)。 下面是例子:
const util = require("util"); const vm = require("vm"); const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }}; vm.createContext(sandbox); const script = new vm.Script(` setTimeout(function(){ globalVar++; cb("async result"); }, 1000); `,{}); script.runInContext(sandbox); console.log(`globalVar: ${sandbox.globalVar}`); // globalVar: 1 // async result代碼運(yùn)行時(shí)間限制
script.runInContext(contextifiedSandbox[, options]) 方法有一個(gè) timeout 選項(xiàng)可以設(shè)定代碼的運(yùn)行時(shí)間,如果超過(guò)時(shí)間就會(huì)拋出錯(cuò)誤,請(qǐng)看下面例子:
const util = require("util"); const vm = require("vm"); const sandbox = {}; const contextifiedSandbox = vm.createContext(sandbox); const script = new vm.Script("while(true){}"); const result = script.runInContext(contextifiedSandbox, {timeout: 1000}); // const result = script.runInContext(contextifiedSandbox, {timeout: 1000}); // ^ // Error: Script execution timed out.
再試試異步代碼,
const util = require("util"); const vm = require("vm"); const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }}; vm.createContext(sandbox); const script = new vm.Script(` setTimeout(function(){ globalVar++; cb("async result"); }, 1000); globalVar; `,{}); const result = script.runInContext(sandbox, {timeout: 500}); console.log(`result: ${result}`); // result: 1 // async result
沒有錯(cuò)誤拋出,也就是說(shuō),這個(gè)選項(xiàng)并不能限制異步代碼的運(yùn)行時(shí)間,那應(yīng)該怎么去限制所有代碼的執(zhí)行時(shí)間呢,目前好像沒有接口終止 vm 代碼的運(yùn)行,如果有異步代碼長(zhǎng)時(shí)間不結(jié)束,很容易造成內(nèi)存泄露,目前可行的方案是使用子進(jìn)程去運(yùn)行代碼,如果超過(guò)限定時(shí)間還沒有結(jié)果,就殺掉該子進(jìn)程,另外,使用子進(jìn)程還可以更方便地對(duì)內(nèi)存等資源進(jìn)行限制。
定制 context 與安全問(wèn)題在一個(gè)全新的 V8 context 里運(yùn)行代碼,里面包含了語(yǔ)言規(guī)范規(guī)定的內(nèi)置的一些函數(shù)和對(duì)象,如果我們想要一些語(yǔ)言規(guī)范之外的功能或者模塊,我們需要把相應(yīng)對(duì)象放到與這個(gè) context 關(guān)聯(lián)的對(duì)象里,例如在上面例子中的這句代碼:
const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }};
setTimeout 不是語(yǔ)言規(guī)范規(guī)定的內(nèi)置函數(shù), context 本身不提供,所以我們需要通過(guò)關(guān)聯(lián)的對(duì)象傳進(jìn)去。
然而,當(dāng)我們把一些模塊功能提供給 context 的時(shí)候,也同時(shí)帶入了更多的安全隱患,請(qǐng)看下面來(lái)自例子:
const util = require("util"); const vm = require("vm"); const sandbox = {}; vm.createContext(sandbox); const script = new vm.Script(` // sandbox 的 constructor 是外層的 Object 類 // Object 類的 constructor 是外層的 Function 類 const OutFunction = this.constructor.constructor; // 于是, 利用外層的 Function 構(gòu)造一個(gè)函數(shù)就可以得到外層的全局 this const OutThis = (OutFunction("return this;"))(); // 得到 require const require = OutThis.process.mainModule.require; // 試試 require("fs"); `,{}); const result = script.runInContext(sandbox); console.log(result === require("fs")); // true
顯然,定制 context 的時(shí)候,任何一個(gè)傳進(jìn)去的對(duì)象或者函數(shù)都可能帶來(lái)上面的問(wèn)題,安全問(wèn)題真的有很多工作需要做。
Github 上有一些開源的模塊用于運(yùn)行不信任代碼,例如 sandbox,vm2,jailed等。查看這些項(xiàng)目的 issue 可以發(fā)現(xiàn),sandbox 和 jailed 都可以用類似上面的方法突破限制,而 vm2 對(duì)這方面做了防護(hù),其它方面也做了更多的安全工作,相對(duì)安全些。
生產(chǎn)中可以考慮在子進(jìn)程中運(yùn)行 vm2, 然后增加更低層的安全限制, 例如限制進(jìn)程的權(quán)限和使用 cgroups 進(jìn)行 IO,內(nèi)存等資源限制,這里不詳細(xì)討論。
總結(jié)本文通過(guò)幾個(gè)例子介紹了 Node.js 的 vm 模塊以及使用 vm 模塊運(yùn)行不信任代碼可能遇到的問(wèn)題,并且對(duì)安全問(wèn)題給出了一些建議。
參考vm
Allowing to terminate a vm context/script
V8 Embedder"s Guide
ECMAScript 2015 語(yǔ)言規(guī)范
sandbox/issues/50
vm2/issues/32
jailed/issues/33
cgroups
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86763.html
摘要:當(dāng)運(yùn)行函數(shù)的時(shí)候,只能訪問(wèn)自己的本地變量和全局變量,不能訪問(wèn)構(gòu)造器被調(diào)用生成的上下文的作用域。如何建立一個(gè)更安全一些的沙箱通過(guò)上文的探究,我們并沒有找到一個(gè)完美的方案在建立安全的隔離的沙箱。 showImg(https://segmentfault.com/img/remote/1460000014575992); 有哪些動(dòng)態(tài)執(zhí)行腳本的場(chǎng)景? 在一些應(yīng)用中,我們希望給用戶提供插入自定義...
摘要:而標(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ù)的替代品,主要解決了安全問(wèn)題。 不安全的vm 在Node.js官方標(biāo)準(zhǔn)庫(kù)中有一個(gè)vm庫(kù),...
摘要:而對(duì)于應(yīng)用越來(lái)越廣泛的而言,運(yùn)行的則是源代碼。通過(guò)查閱的相關(guān)代碼,可以發(fā)現(xiàn)字節(jié)碼的頭部保存著這些信息其中第項(xiàng)就是源代碼長(zhǎng)度。本文同時(shí)發(fā)表于作者個(gè)人博客保護(hù)項(xiàng)目的源代碼 SaaS(Software as a Service,軟件即服務(wù)),是一種通過(guò)互聯(lián)網(wǎng)提供軟件服務(wù)的模式。服務(wù)提供商會(huì)全權(quán)負(fù)責(zé)軟件服務(wù)的搭建、維護(hù)和管理,使得他們的客戶從這些繁瑣的工作中解放出來(lái)。對(duì)于許多中小型企業(yè)而言,S...
摘要:注意此處獲取的數(shù)據(jù)是更新后的數(shù)據(jù),但是獲取頁(yè)面中的元素是更新之前的鉤子函數(shù)說(shuō)明組件已經(jīng)更新,所以你現(xiàn)在可以執(zhí)行依賴于的操作。鉤子函數(shù)說(shuō)明實(shí)例銷毀 Vue -漸進(jìn)式JavaScript框架 介紹 vue 中文網(wǎng) vue github Vue.js 是一套構(gòu)建用戶界面(UI)的漸進(jìn)式JavaScript框架 庫(kù)和框架的區(qū)別 我們所說(shuō)的前端框架與庫(kù)的區(qū)別? Library 庫(kù),本質(zhì)上是一...
摘要:一旦替換已經(jīng)完成,該模塊將被完全棄用。用作錯(cuò)誤處理事件文件,由在標(biāo)準(zhǔn)功能上的簡(jiǎn)單包裝器提供所有模塊都提供這些對(duì)象。 Node.js簡(jiǎn)介 Node 定義 Node.js是一個(gè)建立在Chrome v8 引擎上的javascript運(yùn)行時(shí)環(huán)境 Node 特點(diǎn) 異步事件驅(qū)動(dòng) showImg(https://segmentfault.com/img/bVMLD1?w=600&h=237); no...
閱讀 1056·2022-07-19 10:19
閱讀 1824·2021-09-02 15:15
閱讀 1040·2019-08-30 15:53
閱讀 2686·2019-08-30 13:45
閱讀 2679·2019-08-26 13:57
閱讀 2014·2019-08-26 12:13
閱讀 1032·2019-08-26 10:55
閱讀 579·2019-08-26 10:46