浅析 Node.js 的 vm 模块以及运行不信任代码

2023-11-18

在一些系统中,我们希望给用户提供插入自定义逻辑的能力,除了 RPC 和 REST 之外,运行客户提供的代码也是比较常用的方法,好处是可以极大地减少在网络上的耗时。JavaScript 是一种非常流行而且容易上手的语言,因此,让用户用 JavaScript 来写自定义逻辑是一个不错的选择。下面我们介绍 Node.js 提供的 vm 模块以及分析用它来运行不信任代码可能遇到的问题。

vm 模块

vm 模块是 Node.js 内置的核心模块,它能让我们编译 JavaScript 代码和在指定的环境中运行。请看下面例子:

const util = require('util');
const vm = require('vm'); // 1. 创建一个 vm.Script 实例, 编译要执行的代码 const script = new vm.Script('globalVar += 1; anotherGlobalVar = 1; '); // 2. 用于绑定到 context 的对象 const sandbox = {globalVar: 1}; // 3. 创建一个 context, 并且把 sandbox 这个对象绑定到这个环境, 作为全局对象 const contextifiedSandbox = vm.createContext(sandbox); // 4. 运行上面编译的代码, context 是 contextifiedSandbox const result = script.runInContext(contextifiedSandbox); console.log(`sandbox === contextifiedSandbox ? ${sandbox ===www.bsck.org contextifiedSandbox}`); // sandbox === contextifiedSandbox ? true console.log(`sandbox: ${util.inspect(sandbox)}`); // sandbox: { globalVar: 2, anotherGlobalVar: 1 } console.log(`result: ${util.inspect(result)}`); // result: 1

vm.Script 是一个类,用于创建代码实例,后面可以多次运行。

vm.createContext(sandbox) 用于 "contextify" 一个对象,根据 ECMAScript 2015 语言规范,代码的执行需要一个 execution context。这里的 "contextify",就是把传进去的对象与 V8 的一个新的 context 进行关联。这里所说的关联,我的理解是,这个 "contextified" 对象的属性将会成为那个 context 的全局属性,同时,在 context 下运行代码时产生的全局属性也会成为这个 "contextified" 对象的属性。

script.runInContext(contextifiedSandbox) 就是使代码在 contextifiedSandbox 这个 context 中运行,从上面的输出可以看到,代码运行后,contextifiedSandbox 里面的属性的值已经被改变了,运行结果是最后一个表达式的值。

除了上面几个接口之外,vm 模块还有一些更便捷的接口,例如 vm.runInContext(code, contextifiedSandbox[,www.90168.org options])vm.runInNewContext(code[, sandbox][, options])等,详细可看文档

外层如何得到代码运行结果

我们用 vm 运行代码的时候很可能需要得到一些结果,从上面的例子中可以看到,我们可以通过把结果作为最后一个表达式的值传给外层,或者作为context 的属性给外层使用,这在同步代码里没有问题,但是假如结果需要依赖里面的异步操作呢?这时,我们可以通过在 context 里放一个回调函数。 下面是例子:

const util = require('util');
const vm = require('vm'); const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }}; vm.createContext(sandbox); const script = new vm.Script(` setTimeout(function(){ globalVar++; cb("async result"); }, 1000); `,{}); script.runInContext(sandbox); console.log(`globalVar: ${sandbox.globalVar}`); // globalVar: 1 // async result

代码运行时间限制

script.runInContext(contextifiedSandbox[, options]) 方法有一个 timeout 选项可以设定代码的运行时间,如果超过时间就会抛出错误,请看下面例子: 

const util = require('util');
const vm = require('vm'); const sandbox = {}; const contextifiedSandbox = vm.createContext(sandbox); const script = new vm.Script('while(true){}'); const result = script.runInContext(contextifiedSandbox, {timeout: 1000}); // const result = script.runInContext(contextifiedSandbox, {timeout: 1000}); // ^ // Error: Script execution timed out.

再试试异步代码,

const util = require('util');
const vm = require('vm'); const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }}; vm.createContext(sandbox); const script = new vm.Script(` setTimeout(function(){ globalVar++; cb("async result"); }, 1000); globalVar; `,{}); const result = script.runInContext(sandbox, {timeout: 500}); console.log(`result: ${result}`); // result: 1 // async result

没有错误抛出,也就是说,这个选项并不能限制异步代码的运行时间,那应该怎么去限制所有代码的执行时间呢,目前好像没有接口终止 vm 代码的运行,如果有异步代码长时间不结束,很容易造成内存泄露,目前可行的方案是使用子进程去运行代码,如果超过新视觉限定时间还没有结果,就杀掉该子进程,另外,使用子进程还可以更方便地对内存等资源进行限制。

定制 context 与安全问题

在一个全新的 V8 context 里运行代码,里面包含了语言规范规定的内置的一些函数和对象,如果我们想要一些语言规范之外的功能或者模块,我们需要把相应对象放到与这个 context 关联的对象里,例如在上面例子中的这句代码:

const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) { console.log(result); }};

setTimeout 不是语言规范规定的内置函数, context 本身不提供,所以我们需要通过关联的对象传进去。

然而,当我们把一些模块功能提供给 context 的时候,也同时带入了更多的安全隐患,请看下面来自例子:

const util = require('util');
const vm = require('vm'); const sandbox = {}; vm.createContext(sandbox); const script = new vm.Script(` // sandbox 的 constructor 是外层的 Object 类 // Object 类的 constructor 是外层的 Function 类 const OutFunction = this.constructor.constructor; // 于是, 利用外层的 Function 构造一个函数就可以得到外层的全局 this const OutThis = (OutFunction('return this;'))(); // 得到 require const require = OutThis.process.mainModule.require; // 试试 require('fs'); `,{}); const result = script.runInContext(sandbox); console.log(result === require('fs')); // true

显然,定制 context 的时候,任何一个传进去的对象或者函数都可能带来上面的问题,安全问题真的有很多工作需要做。

Github 上有一些开源的模块用于运行不信任代码,例如 sandboxvm2jailed等。查看这些项目的 issue 可以发现,sandbox 和 jailed 都可以用类似上面的方法突破限制,而 vm2 对这方面做了防护,其它方面也做了更多的安全工作,相对安全些。

生产中光棍影院可以考虑在子进程中运行 vm2, 然后增加更低层的安全限制, 例如限制进程的权限和使用 cgroups 进行 IO,内存等资源限制,这里不详细讨论。

总结

本文通过几个例子介绍了 Node.js 的 vm 模块以及使用 vm 模块运行不信任代码可能遇到的问题,并且对安全问题给出了一些建议。

转载于:https://www.cnblogs.com/tianshifu/p/6379773.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅析 Node.js 的 vm 模块以及运行不信任代码 的相关文章

随机推荐

  • Tensorflow初步之非线性回归

    神经网络 输入 1个神经元 中间层 10个神经元 输出 1个神经元 一 创造二次函数并加入噪声 import tensorflow as tf import matplotlib pyplot as plt import numpy as
  • 干货丨7款易上手C语言编程软件推荐

    C语言是一门历史很长的编程语言 其编译器和开发工具也多种多样 其开发工具包括编译器 现举几个开发工具供大家选择 当然也要根据自己的操作系统来选择适合自己的开发工具 好多刚开始接触c语言的朋友都想知道用上面软件开发c语言比较好 一般来说微软的
  • node版本升级:与node-sass、sass-loader版本不兼容问题

    npm WARN deprecated har validator 5 1 5 this library is no longer supported npm WARN deprecated uuid 3 4 0 Please upgrad
  • 浅谈人工智能:现状、任务、构架与统一

    浅谈人工智能 现状 任务 构架与统一 原创 2017 11 02 朱松纯 目录 引言 第一节 现状 正视现实 第二节 未来 一只乌鸦给我们的启示 第三节 历史 从 春秋五霸 到 战国六雄 第四节 统一 小数据 大任务 范式与认知构架 第五节
  • 《动手学深度学习 Pytorch版》 7.3 网络中的网络(NiN)

    LeNet AlexNet和VGG的设计模式都是先用卷积层与汇聚层提取特征 然后用全连接层对特征进行处理 AlexNet和VGG对LeNet的改进主要在于扩大和加深这两个模块 网络中的网络 NiN 则是在每个像素的通道上分别使用多层感知机
  • nginx 配置ssl后配置websockt

    1 前言 这里以Vue 和 nginx 1 22 0 为例 在nginx配置好ssl为前提下 2 代码 vue 连接路径 state ws new WebSocket wss ssl绑定域名 端口号 ws mediaToChatRoom 端
  • 一个参数一张Excel表,玩转Pandas的read_excel()表格读取

    作者 黄伟呢 来源 数据分析与统计学之美 我觉得很有必要讲述这个文章 进行数据处理的第一步就是Python数据读取 但是你可能没想到 在进行数据读取的同时 我们其实可以配合相关参数做很多事儿 这对于后续的数据处理都是极其有帮助 read e
  • 单元测试的策略

    1 逻辑覆盖 2 循环覆盖 3 同行评审 4 桌前检查 5 代码走查 6 代码评审 7 静态数据流分析 mock 对代码中某些不容易获取的对象创建虚拟对象来测试 stub 桩函数是代替某些被调用了但是没有编写代码 一般在增量迭代自低向上的过
  • Cloud Ace 进军南非——旨在将 Google Cloud 市场扩展到非洲最大的经济体

    本文由Cloud Ace整理发布 Cloud Ace是谷歌云全球战略合作伙伴 拥有 300 多名工程师 也是谷歌最高级别合作伙伴 多次获得 Google Cloud 合作伙伴奖 作为谷歌托管服务商 我们提供谷歌云 谷歌地图 谷歌办公套件 谷
  • [经验] 轻松解读PID控制算法的三种参数的自整定方法

    轻松解读PID控制算法的三种参数的自整定方法 机器人论坛 电子技术论坛 广受欢迎的专业电子论坛 elecfans com
  • MFC实用功能技术——窗口跳转

    MFC系列 0 MFC简易入门基础 1 MFC实用功能技术 窗口跳转 2 MFC实用功能技术 下拉列表添加值 实现的效果是从一个对话框 点击控件能跳转到另一个对话框 实现记录 看了很多别人的博客 要不是太简单自己不懂 要不是实现的有区别 所
  • 【经典】JAVA线程池

    JAVA多线程的五个状态 Running 运行态 该状态下线程池能够接受新的任务 Shutdown 该状态下不接受新的任务 但会继续处理已经添加的任务 Stop 该状态下不接受新的任务 并且会中断正在执行的任务 同时删除未处理的任务 Tid
  • 我国关税

    https zaq us tariff
  • bash: ifconfig: 未找到命令

    Linux CentOS 7 系统使用ifconfig命令不能使用 第一步 尝试安装插件 输入命令 yum install ifconfig 第二步 搜索可用插件 输入命令 yum search ifconfig 第三步 安装对应版本插件工
  • UNIAPP之IOS离线打包

    最新iOS平台SDK下载 https nativesupport dcloud net cn AppDocs download ios 下载SDK解压找到SDK点击打开此文件 HBuilder Hello xcodeproj 打开xcode
  • 【论文速递】CVPR2022 - 用于半监督物体检测的尺度等效蒸馏

    论文速递 CVPR2022 用于半监督物体检测的尺度等效蒸馏 论文原文 Scale Equivalent Distillation for Semi Supervised Object Detection 获取地址 https ieeexp
  • SQL 如何查找一个表里,每个班级各个学科的最高分?

    SQL 如何查找一个表里 每个班级各个学科的最高分 学生表 STUDENT S SNAME SAGE SSEX CLASSNO 班级表 CLASS CLASSNO CLASSNAME 课程表 COURSE C CNAME T 成绩表 SC
  • python代码 从终端输入两个整数m和n,打印m*n的表格

    从终端输入两个整数m和n 打印m n的表格 m int input 请输入第一个数字 n int input 请输入第二个数字 count 1 for i in range m for j in range n print count en
  • navigator.mediaDevices.getUserMedia()出现NotReadableError Could not start audio source错误的解决办法

    问题是 支持了navigator mediaDevices getUserMedia 的方法 但没有权限去调起麦克风导致的问题 解决方案 手动到手机 设置 隐私 授权录音或者麦克风对该app的使用权限
  • 浅析 Node.js 的 vm 模块以及运行不信任代码

    在一些系统中 我们希望给用户提供插入自定义逻辑的能力 除了 RPC 和 REST 之外 运行客户提供的代码也是比较常用的方法 好处是可以极大地减少在网络上的耗时 JavaScript 是一种非常流行而且容易上手的语言 因此 让用户用 Jav