溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node.js中沙箱環(huán)境的示例分析

發(fā)布時(shí)間:2021-08-03 09:15:00 來源:億速云 閱讀:106 作者:小新 欄目:web開發(fā)

這篇文章給大家分享的是有關(guān)Node.js中沙箱環(huán)境的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

node官方文檔里提到node的vm模塊可以用來做沙箱環(huán)境執(zhí)行代碼,對(duì)代碼的上下文環(huán)境做隔離。

\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 rest of the code.

先看一個(gè)例子

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

沙箱環(huán)境中執(zhí)行的代碼對(duì)于外部代碼沒有產(chǎn)生任何影響,無論是新聲明的變量b,還是重新賦值的變量a。 注意最后一行的代碼默認(rèn)會(huì)被加上return關(guān)鍵字,因此無需手動(dòng)添加,一旦添加的話不會(huì)靜默忽略,而是執(zhí)行報(bào)錯(cuò)。

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兩個(gè)方法都可以用來執(zhí)行代碼 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);

由于無法訪問本地的作用域,只能訪問到當(dāng)前的global對(duì)象,因此上面的代碼會(huì)因?yàn)檎也坏絣ocalVal而報(bào)錯(cuò)

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)

如果我們把要執(zhí)行的代碼改成直接賦值的話就可以正常運(yùn)行了,但是也產(chǎn)生了全局污染(全局的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參數(shù)上與runInNewContext有所區(qū)別 runInContext傳入的context對(duì)象不為空而且必須是經(jīng)vm.createContext()處理過的,否則會(huì)報(bào)錯(cuò)。 runInNewContext的context參數(shù)是非必須的,而且無需經(jīng)過vm.createContext處理。 runInNewContext和runInContext因?yàn)橛兄付╟ontext,所以不會(huì)向runInThisContext那樣產(chǎn)生全局污染(不會(huì)產(chǎn)生全局的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

當(dāng)需要一個(gè)沙箱環(huán)境執(zhí)行多個(gè)腳本片段的時(shí)候,可以通過多次調(diào)用runInContext方法但是傳入同一個(gè)vm.createContext()返回值實(shí)現(xiàn)。

超時(shí)控制及錯(cuò)誤捕獲

vm針對(duì)要執(zhí)行的代碼提供了超時(shí)機(jī)制,通過指定timeout參數(shù)即可以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來捕獲代碼錯(cuò)誤

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');
}

延遲執(zhí)行

vm除了即時(shí)執(zhí)行代碼之外,也可以先編譯然后過一段時(shí)間再執(zhí)行,這就需要提到vm.Script了。其實(shí)無論是runInNewContext、runInThisContext還是runInThisContext,背后其實(shí)都創(chuàng)建了Script,從之前的報(bào)錯(cuò)信息就可以看出來 接下來我們就用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也可以做到延遲執(zhí)行,vm.Module主要用來支持ES6 module,而且它的context在創(chuàng)建的時(shí)候就已經(jīng)綁定好了,關(guān)于vm.Module目前還需要在命令行使用flag來啟用支持

node --experimental-vm-module index.js

vm作為沙箱環(huán)境安全嗎?

vm相對(duì)于eval來說更安全一些,因?yàn)樗綦x了當(dāng)前的上下文環(huán)境了,但是盡管如此依然可以訪問標(biāo)準(zhǔn)的JS API和全局的NodeJS環(huán)境,因此vm并不安全,這個(gè)在官方文檔里就提到了

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

請(qǐng)看下面的例子

const vm = require('vm');
vm.runInNewContext("this.constructor.constructor('return process')().exit()")
console.log("The app goes on...") // 永遠(yuǎn)不會(huì)輸出

為了避免上面這種情況,可以將上下文簡(jiǎn)化成只包含基本類型,如下所示

let ctx = Object.create(null);
ctx.a = 1; // ctx上不能包含引用類型的屬性
vm.runInNewContext("this.constructor.constructor('return process')().exit()", ctx);

針對(duì)原生vm存在的這個(gè)問題,有人開發(fā)了vm2包,可以避免上述問題,但是也不能說vm2就一定是安全的

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

雖然執(zhí)行上述代碼沒有問題,但是由于vm2的timeout對(duì)于異步代碼不起作用,所以下面的代碼永遠(yuǎn)不會(huì)執(zhí)行結(jié)束。

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

即使希望通過重新定義Promise的方式來禁用Promise的話,還是一個(gè)可以繞過的

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

感謝各位的閱讀!關(guān)于“Node.js中沙箱環(huán)境的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI