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