首頁 > web前端 > js教程 > Node.js 沙箱環境使用步驟詳解

Node.js 沙箱環境使用步驟詳解

php中世界最好的语言
發布: 2018-05-21 14:25:51
原創
2393 人瀏覽過

這次帶給大家Node.js 沙箱環境使用步驟詳解,Node.js 沙箱環境使用的注意事項有哪些,下面就是實戰案例,一起來看一下。

node官方文件裡提到node的vm模組可以用來做沙箱環境執行程式碼,對程式碼的上下文環境做隔離。

\A common use case is to run the code in a sandboxed environment. The sandboxed code uses a different V8 Context, meaning that it has a different global object than the Context, meaning that it has a different global object than the rest of the .

#先看一個例子

const vm = require('vm');
let a = 1;
var result = vm.runInNewContext('var b = 2; a = 3; a + b;', {a});
console.log(result);  // 5
console.log(a);     // 1
console.log(typeof b); // undefined
登入後複製
沙箱環境中執行的程式碼對外部程式碼沒有產生任何影響,無論是新宣告的變數b,或是重新賦值的變數a。注意最後一行的程式碼預設會被加上return關鍵字,因此無需手動添加,一旦添加的話不會靜默忽略,而是執行報錯。

const vm = require('vm');
let a = 1;
var result = vm.runInNewContext('var b = 2; a = 3; return a + b;', {a});
console.log(result);
console.log(a);
console.log(typeof b);
登入後複製
如下所示

evalmachine.<anonymous>:1
var b = 2; a = 3; return a + b;
         ^^^^^^
SyntaxError: Illegal return statement
  at new Script (vm.js:74:7)
  at createScript (vm.js:246:10)
  at Object.runInNewContext (vm.js:291:10)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:17)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
登入後複製
除了runInNewContext外,vm還提供了runInThisContext和runInContext兩個方法都可以用來執行程式碼runInThisContext無法指定context

const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar += "vm";');
console.log('vmResult:', vmResult);
console.log('localVar:', localVar);
console.log(global.localVar);
登入後複製
由於無法存取本地的作用域,只能存取到目前的global對象,因此上面的程式碼會因為找不到localVal而報錯

evalmachine.<anonymous>:1
localVar += "vm";
^
ReferenceError: localVar is not defined
  at evalmachine.<anonymous>:1:1
  at Script.runInThisContext (vm.js:91:20)
  at Object.runInThisContext (vm.js:298:38)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:21)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
登入後複製
如果我們把要執行的程式碼改成直接賦值的話就可以正常執行了,但也產生了全域污染(全域的localVar變數)

const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult:', vmResult);  // vm
console.log('localVar:', localVar);  // initial value
console.log(global.localVar);     // vm
登入後複製
runInContext在傳入context參數上與runInNewContext有所區別runInContext傳入的context物件不為空而且必須是經vm.createContext( )處理過的,否則會報錯。 runInNewContext的context參數是非必須的,且無需經過vm.createContext處理。 runInNewContext和runInContext因為有指定context,所以不會向runInThisContext那樣產生全域污染(不會產生全域的localVar變數)

const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInNewContext('localVar = "vm";');
console.log('vmResult:', vmResult);  // vm
console.log('localVar:', localVar);  // initial value
console.log(global.localVar);     // undefined
登入後複製
當需要一個沙箱環境執行多個腳本片段的時候,可以透過多次呼叫runInContext方法但是傳入同一個vm.createContext()回傳值實作。

逾時控制及錯誤擷取

vm針對要執行的程式碼提供了逾時機制,透過指定timeout參數即可以runInThisContext為例

const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('while(true) { 1 }; localVar = "vm";', { timeout: 1000});
登入後複製
vm.js:91
   return super.runInThisContext(...args);
          ^
Error: Script execution timed out.
  at Script.runInThisContext (vm.js:91:20)
  at Object.runInThisContext (vm.js:298:38)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:21)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
  at startup (internal/bootstrap/node.js:228:19)
登入後複製
可以透過try catch來捕獲程式碼錯誤

const vm = require('vm');
let localVar = 'initial value';
try { 
  const vmResult = vm.runInThisContext('while(true) { 1 }; localVar = "vm";', {
    timeout: 1000
  });
} catch(e) { 
  console.error('executed code timeout');
}
登入後複製

#延遲執行

#vm除了即時執行程式碼之外,也可以先編譯然後過一段時間再執行,這就需要提到vm.Script了。其實無論是runInNewContext、runInThisContext還是runInThisContext,背後其實都創建了Script,從之前的報錯信息就可以看出來接下來我們就用vm.Script來重寫本文開頭的例子

const vm = require('vm');
let a = 1;
var script = new vm.Script('var b = 2; a = 3; a + b;');
setTimeout(() => { 
  let result = script.runInNewContext({a}); 
  console.log(result);   // 5 
  console.log(a);     // 1 
  console.log(typeof b);  // undefined
}, 300);
登入後複製
除了vm. Script,node在9.6版本中

新增了vm.Module也可以做到延遲執行,vm.Module主要用來支援ES6 module,而且它的context在創建的時候就已經綁定好了,關於vm.Module目前還需要在命令列使用flag來啟用支援

node --experimental-vm-module index.js
登入後複製

#vm作為沙箱環境安全性嗎?

vm相對於eval來說更安全一些,因為它隔離了當前的上下文環境了,但儘管如此依然可以存取標準的JS API和全域的NodeJS環境,因此vm並不安全,這個在官方文檔裡就提到了

The vm module is not a security mechanism. Do not use it to run untrusted code

請看下面的例子

const vm = require('vm');
vm.runInNewContext("this.constructor.constructor('return process')().exit()")
console.log("The app goes on...") // 永远不会输出
登入後複製
為了避免上面這種情況,可以將上下文簡化成只包含基本類型,如下所示

let ctx = Object.create(null);
ctx.a = 1; // ctx上不能包含引用类型的属性
vm.runInNewContext("this.constructor.constructor('return process')().exit()", ctx);
登入後複製
針對原生vm存在的這個問題,有人開發了vm2包,可以避免上述問題,但是也不能說vm2就一定是安全的

const {VM} = require('vm2');
new VM().run('this.constructor.constructor("return process")().exit()');
登入後複製
雖然執行上述程式碼沒有問題,但是由於vm2的timeout對於非同步程式碼不起作用,所以下面的程式碼永遠不會執行結束。

const { VM } = require('vm2');
const vm = new VM({ timeout: 1000, sandbox: {}});
vm.run('new Promise(()=>{})');
登入後複製
即使希望透過重新定義Promise的方式來停用Promise的話,還是一個可以繞過的

const { VM } = require('vm2');
const vm = new VM({ 
 timeout: 1000, sandbox: { Promise: function(){}}
});
vm.run('Promise = (async function(){})().constructor;new Promise(()=>{});');
登入後複製
相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

推薦閱讀:

Vue專案webpack打包部署時Tomcat刷新封包404錯誤問題如何處#理

vue addRoutes實作動態權限路由選單步驟詳解

以上是Node.js 沙箱環境使用步驟詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板