搜尋
首頁web前端js教程如何使用Node.js設定沙箱環境

如何使用Node.js設定沙箱環境

Jun 02, 2018 am 11:40 AM
javascriptnode.js使用

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

有哪些動態執行腳本的場景?

在某些應用程式中,我們希望提供使用者插入自訂邏輯的能力,例如Microsoft 的Office 中的VBA,例如一些遊戲中的lua 腳本,FireFox 的「油猴腳本」,能夠讓使用者發在可控的範圍和權限內發揮想像做一些好玩、有用的事情,擴展了能力,滿足用戶的個人化需求。

大多數都是一些客戶端程序,在一些在線的系統和產品中也常常也有類似的需求,事實上,在線的應用中也有不少提供了自定義腳本的能力,比如穀歌Docs 中的Apps Script,它可以讓你使用JavaScript 做一些非常有用的事情,例如執行程式碼來回應文件開啟事件或儲存格變更事件,為公式製作自訂電子表格函數等等。

與執行在「使用者電腦中」的客戶端應用程式不同,使用者的自訂腳本通常只能影響使用者自已,而對於線上的應用程式或服務來講,有一些情況就變得更為重要,例如“安全”,使用者的“自訂腳本”必須嚴格受到限制和隔離,即不能影響到宿主程序,也不能影響到其它使用者。

而 Safeify 就是一個針對 Nodejs 應用,用於安全執行使用者自訂的非信任腳本的模組。

怎麼安全的執行動態腳本?

我們先來看看通常都能如何在 JavaScript 程式中動態執行一段程式碼?例如大名頂頂的eval

eval('1 2')

上述程式碼沒有問題順利執行了,eval 是全域物件的一個函數屬性,執行的程式碼擁有著和應程中其它正常程式碼一樣的權限,它能存取「執行上下文」中的局部變量,也能存取所有「全域變數」,在這個場景下,它是一個非常危險的函數。

再來看看Functon,透過Function 建構器,我們可以動態的建立一個函數,然後執行它

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

它也一樣的順利執行了,使用Function 建構器產生的函數,並不會在創建它的上下文中創建閉包,一般在全域作用域中被創建。當運行函數的時候,只能存取自己的本地變數和全域變量,不能存取 Function 構造器被呼叫產生的上下文的作用域。如同一個站在地上、一個站在一張薄薄的紙上一樣,在這個場景下,幾乎沒有高下之分。

結合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) {
  // 让动态执行的代码认为属性已存在
  return true; 
 }
 });
 return fn(proxy);
}
evalute('1+2') // 3
evalute('console.log(1)') // Cannot read property 'log' of undefined

我們知道無論eval 還是function,執行時都會把作用域一層一層向上查找,如果找不到會一直到global,那麼利用Proxy 的原理就是,讓執行了程式碼在sandobx 中找的到,以達到「防逃逸」的目的。

在瀏覽器中,也可以利用 iframe,創造一個再發安全的一些隔離環境,本文也著眼於 Node.js,在這裡不做過多討論。
在 Node.js 中呢,有沒有其它選擇?

或許沒看到這裡之前你就已經想到了VM,它是Node.js 預設就提供的一個內建模區塊,VM 模組提供了一系列API 用於在V8 虛擬機環境中編譯和運行程式碼。 JavaScript 程式碼可以被編譯並立即執行,或編譯、儲存然後再運行。

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

執行上這的程式碼就能拿到結果3,同時,透過vm.Script 還能指定程式碼執行了「最大毫秒數」,超過指定的時長將終止執行並拋出一個例外

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

上面的腳本執行將會失敗,被偵測到逾時並拋出異常,然後被Try Cache 捕獲並打出log,但同時需要注意的是vm.Script 的timeout選項「只針對同步代有效」,而不包括是異步調用的時間,例如

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

上述代码,并不是会在 50ms 后抛出异常,因为 50ms 上边的代码同步执行肯定完了,而 setTimeout 所用的时间并不算在内,也就是说 vm 模块没有办法对异步代码直接限制执行时间。我们也不能额外通过一个 timer 去检查超时,因为检查了执行中的 vm 也没有方法去中止掉。

另外,在 Node.js 通过 vm.runInContext 看起来似乎隔离了代码执行环境,但实际上却很容易「逃逸」出去。

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

执行上边的代码,宿主程序立即就会「退出」,sandbox 是在 VM 之外的环境创建的,需 VM 中的代码的 this 指向的也是 sandbox,那么

//this.constructor 就是外所的 Object 构建函数
const ObjConstructor = this.constructor; 
//ObjConstructor 的 constructor 就是外包的 Function
const Function = ObjConstructor.constructor;
//创建一个函数,并执行它,返回全局 process 全局对象
const process = (new Function('return process'))(); 
//退出当前进程
process.exit();

没有人愿意用户一段脚本就能让应用挂掉吧。除了退出进程序之外,实际上还能干更多的事情。

有个简单的方法就能避免通过 this.constructor 拿到 process,如下:

const vm = require('vm');
//创建一外无 proto 的空白对象作为 sandbox
const sandbox = Object.create(null);
const script = new vm.Script('...');
const context = vm.createContext(sandbox);
script.runInContext(context);

但还是有风险的,由于 JavaScript 本身的动态的特点,各种黑魔法防不胜防。事实 Node.js 的官方文档中也提到 VM 当做一个安全的沙箱去执行任意非信任的代码。

有哪些做了进一步工作的社区模块?

在社区中有一些开源的模块用于运行不信任代码,例如 sandbox、vm2、jailed 等。相比较而言 vm2 对各方面做了更多的安全工作,相对安全些。

从 vm2 的官方 READM 中可以看到,它基于 Node.js 内建的 VM 模块,来建立基础的沙箱环境,然后同时使用上了文介绍过的 ES6 的 Proxy 技术来防止沙箱脚本逃逸。

用同样的测试代码来试试 vm2

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

如上代码,并没有成功结束掉宿主程序,vm2 官方 REAME 中说「vm2 是一个沙盒,可以在 Node.js 中按全的执行不受信任的代码」。

然而,事实上我们还是可以干一些「坏」事情,比如:

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

上边的代码将永远不会执行结束,如同 Node.js 内建模块一样 vm2 的 timeout 对异步操作是无效的。同时,vm2 也不能额外通过一个 timer 去检查超时,因为它也没有办法将执行中的 vm 终止掉。这会一点点耗费完服务器的资源,让你的应用挂掉。

那么或许你会想,我们能不能在上边的 sandbox 中放一个假的 Promise 从而禁掉 Promise 呢?答案是能提供一个「假」的 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 了。从另一个层面来看,况且或许有时我们还想让自定义脚本支持异步处理呢。

如何建立一个更安全一些的沙箱?

通过上文的探究,我们并没有找到一个完美的方案在 Node.js 建立安全的隔离的沙箱。其中 vm2 做了不少处理,相对来讲算是较安全的方案了,但问题也很明显,比如异步不能检查超时的问题、和宿主程序在相同进程的问题。

没有进程隔离时,通过 VM 创建的 sanbox 大体是这样的

那么,我们是不是可以尝试,将非受信代码,通过 vm2 这个模块隔离在一个独立的进程中执行呢?然后,执行超时时,直接将隔离的进程干掉,但这里我们需要考虑如下几个问题

通过进程池统调度管理沙箱进程

如果来一个执行任务,创建一个进程,用完销毁,仅处理进程的开销就已经稍大了,并且也不能不设限的开新进程和宿主应用抢资源,那么,需要建一个进程池,所有任务到来会创建一个 Script 实例,先进入一个 pending 队列,然后直接将 script 实例的 defer 对象返回,调用处就能 await 执行结果了,然后由 sandbox master 根据工程进程的空闲程序来调度执行,master 会将 script 的执行信息,包括重要的 ScriptId,发送给空闲的 worker,worker 执行完成后会将「结果 + script 信息」回传给 master,master 通过 ScriptId 识别是哪个脚本执行完毕了,就是结果进行 resolve 或 reject 处理。

这样,通过「进程池」即能降低「进程来回创建和销毁的开销」,也能确保不过度抢占宿主资源,同时,在异步操作超时,还能将工程进程直接杀掉,同时,master 将发现一个工程进程挂掉,会立即创建替补进程。

处理的数据和结果,还有公开给沙箱的方法

进程间如何通讯,需要「动态代码」处理数据可以直接序列化后通过 IPC 发送给隔离 Sandbox 进程,执行结果一样经过序列化通过 IPC 传输。

其中,如果想法公开一个方法给 sandbox,因为不在一个进程,并不能方便的将一个方案的引用传递给 sandbox。我们可以将宿主的方法,在传递给 sandbox worker 之类做一下处理,转换为一个「描述对象」,包括了允许 sandbox 调用的方法信息,然后将信息,如同其它数据一样发送给 worker 进程,worker 收到数据后,识出来所「方法描述对象」,然后在 worker 进程中的 sandbox 对象上建立代理方法,代理方法同样通过 IPC 和 master 通讯。

最终,我们建立了一个大约这样的「沙箱环境」

如此这般处理起来是不是感觉很麻烦?但我们就有了一个更加安全一些的沙箱环境了,这些处理。笔者已经基于 TypeScript 编写,并封装为一个独立的模块 Safeify。

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

最后,简单介绍一下 Safeify 如何使用,通过如下命令安装

npm i safeify --save

在应用中使用,还是比较简单的,如下代码(TypeScript 中类似)

import { Safeify } from './Safeify';
const safeVm = new Safeify({
 timeout: 50,     //超时时间,默认 50ms
 asyncTimeout: 500,  //包含异步操作的超时时间,默认 500ms
 quantity: 4      //沙箱进程数量,默认同 CPU 核数
});
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);

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

怎样操作vue2.0 移动端实现下拉刷新和上拉加载

怎样使用vue计算属性与方法侦听器

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

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

在Quartz中如何在任務開始前發送通知?在Quartz中如何在任務開始前發送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前發送任務通知在使用Quartz定時器進行任務調度時,任務的執行時間是由cron表達式設定的。現�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。