关于javascript:nodejs-沙盒逃逸分析

11次阅读

共计 2189 个字符,预计需要花费 6 分钟才能阅读完成。

作者:凹凸曼 – nobo

背景

日常开发需要中有时候为了谋求灵活性或升高开发难度,会在业务代码里间接应用 eval/Function/vm 等性能,其中 eval/Function 算是动静执行 JS,但无奈屏蔽以后执行环境的上下文,但 node.js 里提供了 vm 模块,相当于一个虚拟机,能够让你在执行代码时候隔离以后的执行环境,防止被恶意代码攻打。

vm 根本介绍

vm 模块可在 V8 虚拟机上下文中编译和运行代码,虚拟机上下文可自行配置,利用该个性做到沙盒的成果。例如:

const vm = require("vm");

const x = 1;
const y = 2;

const context = {x: 2, console};
vm.createContext(context); // 上下文隔离化对象。const code = "console.log(x); console.log(y)";

vm.runInContext(code, context);
// 输入 2
// Uncaught ReferenceError: y is not defin

依据以上示例,能够看出和 eval/Function 最大的区别就是可自定义上下文,也就能够管制被执行代码的拜访资源。例如以上示例,除了语言的语法、内置对象等,无法访问到超出上下文外的任何信息,所以示例中呈现了谬误提醒: y 未定义。以下是 vm 的的执行示例图:

沙盒环境代码只能读取 VM 上下文 数据。

沙盒逃逸

node.js 在 vm 的文档页上有如下形容:

vm 模块不是平安的机制。不要应用它来运行不受信赖的代码。

刚开始看到这句话的很好奇,为什么会这样?依照方才的了解他应该是平安的?搜寻后咱们找到一段逃逸示例:

const vm = require("vm");

const ctx = {};

vm.runInNewContext('this.constructor.constructor("return process")().exit()',
    ctx
);
console.log("Never gets executed.");

以上示例中 this 指向 ctx 并通过原型链的形式拿到沙盒外的 Funtion,实现逃逸,并执行逃逸后的 JS 代码。

以上示例大抵拆分:

tmp = ctx.constructor; // Object

exec = tmp.constructor; // Function

exec("return Process");

以上是通过原型链形式实现逃逸,如果将上下文对象的原型链设置为 null 呢?

const ctx = Object.create(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,从而实现逃逸,拿到全局数据,示例图如下:

总结

因为语言的个性,在沙盒环境下通过原型链的形式能获取全局的 Function,并通过它来执行代码。

最终的确如官网所说,在应用 vm 的时应确保所运行的代码是可信赖的。

eval/Function/vm 等可动静执行代码的性能在 JavaScript 里肯定是用来执行可信赖代码。

以下可能是比拟常见会用到动静执行脚本的场景:模板引擎,H5 游戏、谋求高度灵便配置的场景。

解决方案

  • 事先解决,如:代码平安扫描、语法限度
  • 应用 vm2 模块,它的实质就是通过代理的形式来进行平安校验,尽管也可能还存在未呈现的逃逸形式,所以在应用时也审慎看待。
  • 本人实现解释器,并在解释器层接管所有对象创立及属性拜访。

欢送关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。

正文完
 0