目录
3.1 this去获取一个toString方法,再通过toString方法来获取构造函数,拿到process模块
问题1:为什么我们不直接使用{}.toString.constructor('return process')(),却要使用this呢?
问题2:m和n也是沙盒外的对象,为什么也不能用m.toString.constructor('return process')()呢?
一:沙箱绕过
1:概念
Node.js沙箱逃逸漏洞是一种安全漏洞,它允许攻击者通过利用Node.js沙箱环境中的漏洞来逃逸出沙箱,并访问或控制沙箱之外的系统资源。
沙箱是一种隔离技术,用于限制代码运行的环境,以防止恶意代码对系统造成损害。在Node.js中,沙箱通常用于运行不受信任的代码,例如第三方模块或用户提交的代码。
2:核心原理
只要我们能在沙箱内部,找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱。
3:例题分析
3.1 this去获取一个toString方法,再通过toString方法来获取构造函数,拿到process模块
vm是 Node.js 的内置模块,用于在本机代码中执行 JavaScript 代码。使用 vm
模块,可以创建一个隔离的环境,以执行不受信任的代码,从而避免对主机环境的直接访问和潜在风险。
const vm = require('vm');
const script = `m + n`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)
创建了一个名为 sandbox 的对象,它具有两个属性:m 和 n,分别被初始化为 1 和 2。这个对象代表了一个隔离的环境,我们的代码将在其中执行。
然后,我们使用 vm.createContext() 创建了一个新的上下文对象 context,并将 sandbox 作为参数传递给它。这样,context 就成为了一个与 sandbox 具有相同属性和方法的对象。
最后,我们使用 vm.runInContext() 方法来在指定的上下文中执行代码。我们将 script 和 context 作为参数传递给这个方法,它会返回执行结果。
const vm = require('vm');
const script = `
const process = this.toString.constructor('return process')()
process.mainModule.require('child_process').execSync('whoami').toString()
`;
const sandbox = {m:[], n: {}, x: /regexp/};
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)
通过this去获取一个toString方法,再通过toString方法来获取构造函数
怎么获取Function,通过toString函数和constructor 属性来获取Function这个属性
this.toString.constructor 因为constructor本身就是指向它的构造函数
先拿到个process模块,通过this(这个this指向的是sandbox全局).toString.constructor拿到所有函数的构造函数Function,接着拿到process模块,最终拿到子模块child_process调用命令执行
问题1:为什么我们不直接使用{}.toString.constructor('return process')(),却要使用this呢?
空对象调用toString.constructor也能拿到Function,但是空对象是沙盒内部的,沙盒逃逸就是要把外部的对象元素引入进来才行,不把外部元素引入进来的话,逃逸不出来。
这两个的一个重要区别就是,{}是在沙盒内的一个对象,而this是在沙盒外的对象(注入进来的)。沙盒内的对象即使使用这个方法,也获取不到process,因为它本身就没有process。
问题2:m和n也是沙盒外的对象,为什么也不能用m.toString.constructor('return process')()呢?
m和n也是在外部定义的,也有toString方法,不能的原因是原始类型是值引用而不是类型引用,
如果是值引用,外部的m和n和沙盒内部的mn是两个数据,如果是类型引用,那就是外部的m把内存地址传给内部,内部直接调用外部的内存地址,那这俩就是一样的
这个原因就是因为primitive types,数字、字符串、布尔等这些都是primitive types,他们的传递其实传递的是值而不是引用,所以在沙盒内虽然你也是使用的m,但是这个m和外部那个m已经不是一个m了,所以也是无法利用的
问题3:想一想能不能把mn的值引用变成类型引用呢?
可以把mn改成空对象或者空数组甚至正则表达式,这样在外部就是类型引用了,这样内部就直接调用的是内存地址 那内外的mn就是一样的了
context:{m: [], n: {}, x: /regexp/}
这边就能看到执行了任意命令了,这里的whoami执行就是我的主机名。逃逸出沙箱就一种方法,就是拿到沙箱外部的变量或对象,然后用.toString方法和.constructor 属性来获取Function这个属性,然后拿到process,之后就可以执行任意代码了
命令随便执行的,给个ipconfig也是可以执行的
这就是典型的沙箱逃逸来执行命令,如果创建木马啥的,那就会造成危害了