欢迎光临
Html5圈

js的沙箱内容

  市面上现在流行两种沙箱模式,一种是使用iframe,还有一种是直接在页面上使用new Function + eval进行执行. 殊途同归,主要还是防止一些Hacker们 吃饱了没事干,收别人钱来 Hack 你的网站. 一般情况, 我们的代码量有60%业务+40%安全. 剩下的就看天意了.

  接下来,我们来一步一步分析,如果做到在前端的沙箱.文末 看俺有没有心情放一个彩蛋吧.

  直接嵌套


  这种方式说起来并不是什么特别好的点子,因为需要花费比较多的精力在安全性上.

  eval执行

  最简单的方式,就是使用eval进行代码的执行

eval('console.log("a simple script");');

  但,如果你是直接这么使用的话, congraduations… do die…

  因为,eval 的特性是如果当前域里面没有,则会向上遍历.一直到最顶层的global scope 比如window.以及,他还可以访问closure内的变量.看demo:

function Auth(username)
{
  var password = "trustno1";
  this.eval = function(name) { return eval(name) } // 相当于直接this.name
}

auth = new Auth("Mulder")
console.log(auth.eval("username")); // will print "Mulder"
console.log(auth.eval("password")); // will print "trustno1"

  那有没有什么办法可以解决eval这个特性呢?

  答: 没有. 除非你不用

  ok,那我就不用. 我们这里就可以使用new Function(..args,bodyStr) 来代替eval.

  new Function

  new Function就是用来,放回一个function obj的. 用法参考:new Function.

  所以,上面的代码,放在new Function中,可以写为:

new Function('console.log("a simple script");')();

  这样做在安全性上和eval没有多大的差别,不过,他不能访问closure的变量,即通过this来调用,而且他的性能比eval要好很多. 那有没有办法解决global var的办法呢?

  有啊… 只是有点复杂先用with,在用Proxy

  with

  with这个特性,也算是一个比较鸡肋的,他和eval并列为js两大SB特性. 不说无用, bug还多,安全性就没谁了… 但是, with的套路总是有人喜欢的.在这里,我们就需要使用到他的特性.因为,在with的scope里面,所有的变量都会先从with定义的Obj上查找一遍.

var a = {
    c:1
}
var c =2;
with(a){
    console.log(c); //等价于c.a
}

  所以,第一步改写上面的new Function(),将里面变量的获取途径控制在自己的手里.

function compileCode (src) {  
  src = 'with (sandbox) {' + src + '}'
  return new Function('sandbox', src)
}

  这样,所有的内容多会从sandbox这个str上面获取,但是找不到的var则又会向上进行搜索. 为了解决这个问题,则需要使用: proxy

  proxy

  es6 提供的Proxy特性,说起来也是蛮牛逼的. 可以将获取对象上的所有方式改写.具体用法可以参考: 超好用的proxy.

  这里,我们只要将has给换掉即可. 有的就好,没有的就返回undefined

function compileCode (src) {
  src = 'with (sandbox) {' + src + '}'
  const code = new Function('sandbox', src)

  return function (sandbox) {
    const sandboxProxy = new Proxy(sandbox, {has})
    return code(sandboxProxy)
  }
}

// 相当于检查 获取的变量是否在里面 like: 'in'
function has (target, key) {
  return true
}

compileCode('log(name)')(console);

  这样的话,就能完美的解决掉 向上查找变量的烦恼了. 另外一些大神,发现在新的ECMA里面,有些方法是不会被with scope 影响的. 这里,主要是通过Symbol.unscopables 这个特性来检测的.比如:

Object.keys(Array.prototype[Symbol.unscopables]); 
// ["copyWithin", "entries", "fill", "find", "findIndex", 
//  "includes", "keys", "values"]

  不过,经过本人测试发现也只有Array.prototype上面带有这个属性… 尴尬… 所以,一般而言,我们可以加上 Symbol.unscopables, 也可以不加.

// 还是加一下吧
function compileCode (src) {  
  src = 'with (sandbox) {' + src + '}'
  const code = new Function('sandbox', src)

  return function (sandbox) {
    const sandboxProxy = new Proxy(sandbox, {has, get})
    return code(sandboxProxy)
  }
}

function has (target, key) {  
  return true
}

function get (target, key) {  
// 这样,访问Array里面的 like, includes之类的方法,就可以保证安全... 算了,就当我没说,真的没啥用...
  if (key === Symbol.unscopables) return undefined
  return target[key]
}

  现在,基本上就可以宣告你的代码是99.999% 的5位安全数.(反正不是100%就行)

  设置缓存

  如果上代码,每次编译一次code时,都会实例一次Proxy, 这样做会比较损性能. 所以,我们这里,可以使用closure来进行缓存。 上面生成proxy代码,改写为:

function compileCode(src) {
    src = 'with (sandbox) {' + src + '}'
    const code = new Function('sandbox', src)

    function has(target, key) {
        return true
    }

    function get(target, key) {
        if (key === Symbol.unscopables) return undefined
        return target[key]
    }

    return (function() {
        var _sandbox, sandboxProxy;
        return function(sandbox) {
            if (sandbox !== _sandbox) {
                _sandbox = sandbox;
                sandboxProxy = new Proxy(sandbox, { has, get })
            }
            return code(sandboxProxy)
        }
    })()
}

  不过上面,这样的缓存机制有个弊端,就是不能存储多个proxy. 不过,你可以使用Array来解决,或者更好的使用Map. 这里,我们两个都不用,用WeakMap来解决这个problem. WeakMap 主要的问题在于,他可以完美的实现,内部变量和外部的内容的统一. WeakMap最大的特点在于,他存储的值是不会被垃圾**机制关注的. 说白了, WeakMap引用变量的次数是不会算在引用垃圾**机制里, 而且, 如果WeakMap存储的值在外部被垃圾**装置**了,WeakMap里面的值,也会被删除–同步效果.所以,毫无意外, WeakMap是我们最好的一个tricky. 则,代码可以写为:

const sandboxProxies = new WeakMap()
function compileCode(src) {
    src = 'with (sandbox) {' + src + '}'
    const code = new Function('sandbox', src)

    function has(target, key) {
        return true
    }

    function get(target, key) {
        if (key === Symbol.unscopables) return undefined
        return target[key]
    }
    return function(sandbox) {
        if (!sandboxProxies.has(sandbox)) {
            const sandboxProxy = new Proxy(sandbox, { has, get })
            sandboxProxies.set(sandbox, sandboxProxy)
        }
        return code(sandboxProxies.get(sandbox))
    }
}

  差不多了, 如果不嫌写的丑,可以直接拿去用.(如果出事,纯属巧合,本人概不负责).

  接着,我们来看一下,如果使用iframe,来实现代码的编译. 这里,Jsfiddle就是使用这种办法.

  iframe 嵌套

  最简单的方式就是,使用sandbox属性. 该属性可以说是真正的沙盒… 把sandbox加载iframe里面,那么,你这个iframe基本上就是个标签而已… 而且支持性也挺棒的,比如IE10.

function Auth(username)
{
  var password = "trustno1";
  this.eval = function(name) { return eval(name) } // 相当于直接this.name
}

auth = new Auth("Mulder")
console.log(auth.eval("username")); // will print "Mulder"
console.log(auth.eval("password")); // will print "trustno1"

0

  这样已添加,那么下面的事,你都不可以做了:

  1. script脚本不能执行

  2. 不能发送ajax请求

  3. 不能使用本地存储,即localStorage,cookie等

  4. 不能创建新的弹窗和window, 比如window.open or target=”_blank”

  5. 不能发送表单

  6. 不能加载额外插件比如flash等

  7. 不能执行自动播放的tricky. 比如: autofocused, autoplay

  看到这里,我也是醉了。 好好的一个iframe,你这样是不是有点过分了。 不过,你可以放宽一点权限。在sandbox里面进行一些简单设置

function Auth(username)
{
  var password = "trustno1";
  this.eval = function(name) { return eval(name) } // 相当于直接this.name
}

auth = new Auth("Mulder")
console.log(auth.eval("username")); // will print "Mulder"
console.log(auth.eval("password")); // will print "trustno1"

1

  常用的配置项有:

未经允许不得转载:Html5圈 » js的沙箱内容

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

HTML5网站定制 更优秀 更专业

联系我们全国服务热线
配置 效果
allow-forms 允许进行提交表单
allow-scripts 运行执行脚本
allow-same-origin 允许同域请求,比如ajax,storage
allow-top-navigation 允许iframe能够主导window.top进行页面跳转
allow-popups 允许iframe中弹出新窗口,比如,window.open,target=”_blank”
allow-pointer-lock 在iframe中可以锁定鼠标,主要和鼠标锁定有关