node-沙箱

浏览器端沙箱

js-浏览器沙箱-CSDN博客

问题😭

        在服务端,除了要对网络请求进行拦截,限制访问全局变量(process、require等),还要拦截访问文件资源,限制cpu和内存的使用等。

解决方案

1、vm2💞


vm2主要用于创建一个安全的沙箱环境,基于node内置模块vm实现,防止恶意代码访问主进程的资源。

使用

1、我们可以通过new VM来创建vm实例,然后vm.run实例方法执行脚本。

2、在创建vm实例时,我们可以通过sandbox属性重写一些全局方法(require、process等),从而进行拦截

3、我们可以通过process.cpuUsage()和process.memoryUsage()来监控cpu和内存的使用情况。

const { VM } = require('vm2')
const fs = require('fs')
const os = require('os')

function runInSandbox(code, timeLimit = 1000, memoryLimit = 50 * 1024 * 1024) {
  const vm = new VM({
    timeout: timeLimit,
    sandbox: {
      // 选择性暴露某些API
      console: console,
      require: (moduule) => {
        return require(moduule)
      },
      readFile: (path) => {
        // 可以在这里添加额外的安全检查
        return fs.readFileSync(path, 'utf8')
      },
      makeRequest: (url) => {
        // 自定义的网络请求函数,可以在这里实现拦截逻辑
        console.log(`请求被拦截:${url}`)
        return '模拟的响应数据'
      }
    }
  })

  const startCPU = process.cpuUsage()
  const startMem = process.memoryUsage()

  let intervalId
  try {
    intervalId = setInterval(() => {
      const currentMem = process.memoryUsage()
      if (currentMem.heapUsed - startMem.heapUsed > memoryLimit) {
        clearInterval(intervalId)
        throw new Error('内存使用超出限制')
      }
    }, 100)

    const result = vm.run(code)

    clearInterval(intervalId)

    const endCPU = process.cpuUsage(startCPU)
    const endMem = process.memoryUsage()

    console.log('CPU使用 (微秒):', endCPU.user + endCPU.system)
    console.log('内存使用 (字节):', endMem.heapUsed - startMem.heapUsed)

    return result
  } catch (err) {
    clearInterval(intervalId)
    console.error('执行错误:', err)
  }
}

// 使用示例
const code = `
    let sum = 0;
    for (let i = 0; i < 1000000; i++) {
        sum += i;
    }
    sum;
`

console.log('执行结果:', runInSandbox(code))

2、isolated-vm💕

vm2的官网推荐用ivm来构建沙箱,更安全吧。

特点:

1、isolated-vm提供了完全隔离的v8实例,每个实例有独立的内存和上下文。

2、支持不同实例之间通信。

3、提供更详细的控制,可以精确控制内存和资源的使用。

4、比vm2更安全。【主要vm2不维护了,也推荐ivm,ivm是更安全吧,但是用起来很麻烦....😭】

使用

1、内存限制可以在new ivm实例时直接设置,而不用手动来监听。

2、宿主给沙箱的变量、函数等,需要通过new ivm.Reference(a)包裹处理。

3、宿主函数的返回值为引用类型时,需要new ivm.ExternalCopy(result).copyInto()进行处理。

4、context.global.set()用于设置沙箱环境的全局变量,如果一个全局函数是返回promise,需要借用applySyncPromise来调用才能保证顺序。

具体代码如下:

const ivm = require('isolated-vm')
const axios = require('axios')

;(async () => {
  const isolate = new ivm.Isolate({ memoryLimit: 128 })
  const context = await isolate.createContext()
  const jail = context.global
  await jail.set('global', jail.derefInto())

  // 创建一个宿主函数
  const hostFunction = async function (url, option = { method: 'get' }) {
    try {
      const response = await axios.request({ url, ...option })
      console.log(response.data, '宿主函数的结果')
      return response.data
    } catch (error) {
      console.error('宿主函数错误:', error.message)
      return { error: error.message }
    }
  }

  // 创建一个包装函数
  const wrapperFunction = new ivm.Reference(async function (...args) {
    const result = await hostFunction.apply(undefined, args)
    return new ivm.ExternalCopy(result).copyInto() //引用类型的结果这样处理
  })
  await jail.set('hostFunctionWrapper', wrapperFunction)

  // 在沙箱中定义一个普通函数来调用包装函数
  await context.evalClosure(
    `
    global.hostFunction = async function(...args) {
      try {
        const res = await hostFunctionWrapper.applySyncPromise(undefined, args); 
        //promise类的需要用applySyncPromise调用,其他的可以用apply
        return res;
      } catch(err) {
        console.log(err, '沙箱错误');
        throw err;
      }
    };
  `,
    [wrapperFunction] //可以传递空数组,除非要用$0
  )

  // 拦截console
  const consoleFn = new ivm.Reference(function (...args) {
    console.log(...args.map((arg) => (typeof arg === 'string' ? arg : JSON.stringify(arg))))
  })
  await jail.set('consoleFn', consoleFn)
  await context.evalClosure(`
    global.console = {
      log: function(...args) {
        return consoleFn.apply(undefined, args);
      }
    };
  `)

  const userCode = `
    (async function(){
      console.log('开始执行沙箱代码');
      try {
        let res = await hostFunction('http://127.0.0.1:3000/getData');
        console.log(res.msg, '请求结果');
      } catch(err) {
        console.log(err, '请求失败');
      }
      console.log('沙箱代码执行完毕');
    })();
  `

  try {
    await context.eval(userCode)
    // 等待异步操作完成
    await new Promise((resolve) => setTimeout(resolve, 1000))
  } catch (err) {
    console.error('Error executing script:', err)
  }

  // 释放引用
  wrapperFunction.release()
  consoleFn.release()
})()

END🤣

1、如果觉得ivm用起来麻烦,就用vm2。

2、出了做沙箱,更应该对脚本进行日志记录😚

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值