natevw / evel Goto Github PK
View Code? Open in Web Editor NEW[attempt at] safe eval of untrusted JavaScript source code in the browser.
Home Page: http://natevw.github.io/evel/challenge.html
[attempt at] safe eval of untrusted JavaScript source code in the browser.
Home Page: http://natevw.github.io/evel/challenge.html
Evel'ing this successfully returns a Promise:
import('test')
Testing in Chrome 80, the promise gets rejected with an Error: Cannot import module from an inactive browsing context.
but it's possible that may vary depending on JS engine and/or iframe sandbox attribute support.
Dynamic import()
works via a "function-like" keyword, not an actual call to a built-in function (e.g. it's like if
or catch
, rather than like eval
or isNaN
). We are unable to block keywords through shadowing (i.e. Function('import', "")
throws just like var yield = return;
would).
Even the Agoric/realms-shim ends up having to munge incoming source on account of this very issue: https://github.com/Agoric/realms-shim/blob/025d975da12c8033022c271e5d99a3810066dfa4/src/sourceParser.js#L31
while(true);
or regexps that abuse lookahead to get exponential execution times?
Figured I'd open a thread to list "see alsos".
ES6 adds many new globals, and we have a hardcoded list :-(
Note that some, like Reflect
may expose new functionality that needs to be reviewed, see #25.
Web workers have a different name for their global context. We should handle this in our strict-mode global finding case.
Welp, this one is too easy:
http://natevw.github.io/evel/challenge.html#Function(%22return%20this%22)()
Credit: https://twitter.com/wisecwisec/status/357861675782770689
Need to dive into the spec and figure out why Function
breaks out of strict mode while eval
doesn't — this could be intentional and if so we're ± hosed? We could pass in evel.Function
as Function
, but I suspect it's trivial to get the original back via primitives and any other exposed methods.
Heh, hunting these down sure is fun.
eval('this')
//=> undefined
eval('eval')('this')
//=> [object Window]
http://natevw.github.io/evel/challenge.html#eval('eval')('this')
Okay, so here's how I understand #15 works mostly for @kumavis but also adding some followup items I want to check.
According to ES5, even a literal Function Definition follows the algorithm for Creating Function Objects. This algorithm sets the prototype of the new object to "the standard built-in Function prototype object".
This is why I gave up before, but your trick is that as section 15.3 of the spec describes, this "standard built-in" object can still be configured like any other.
The heart of the matter is this line of code:
_gObj.Function.prototype.constructor = evel.Function;
[Note that I’ve simplified it! You were assigning _gObj.Function.constructor.prototype.constructor
which is equivalent to _gObj.Function.prototype.constructor.prototype.constructor
(since Function.hasOwnProperty('constructor') === false
). But Function.prototype.constructor === Function
already, which is where the simplified version above comes from.]
So anyway, the way the environment starts out is with (to use V8’s naming) Function.prototype = function Empty() {}
. ES5 simply specifies that Function.prototype
is a function that accepts any arguments and returns undefined
. To quote 15.3.4.1 verbatim: "The initial value of Function.prototype.constructor is the built-in Function constructor." This is what gave us the #12 exploit.
So, the only magic we really have to do is change the constructor value from the initial Function
to evel.Function
. Assigning _gObj.Function
is just taking care of the more obvious access.
We need to assign evel.Function.constructor = evel.Function
while setting up the iframe because we have fixed up the iframe context’s Function.prototype.constructor
whereas evel.Function still has its original "standard built-in Function" proto from when generated on the main window. (This confused me for a bit: the constructor assignment to self in step 17 of 13.2 happens on the Function.prototype
object not the Function.__proto__
/Object.getPrototypeOf(Function)
one.)
So I think this just might work (great job @kumavis) but I also think there may be some loose ends lying around:
evel.Function.constructor
simply shadows the original Object.getPrototypeOf(evel.Function).constructor
and so this needs a more robust work.Right now the function wrapper has to crawl through the window
prototype chain every time it is called. And once we integrate the iframe trick in #1 that setup would add additional overhead.
In some cases with repeated calls to wrapped functions where decent performance is desirable (e.g. calling a PouchDB map function over a whole bunch of documents) the caller may know that globals haven't been added. We could add a .evel_reuseSandbox
property on the wrapped function to speed up repeated calls.
This code (borrowed from @mathiasbynens's clever globalThis
polyfill) is able to return the window object (of the iframe):
(function() {
//if (typeof globalThis === 'object') return;
Object.defineProperty(Object.prototype, '__magic__', {
get: function() {
return this;
},
configurable: true // This makes it possible to `delete` the getter later.
});
__magic__.globalThis = __magic__; // lolwat
var tmp = __magic__;
delete Object.prototype.__magic__;
return tmp;
}())
Most of the properties e.g. XMLHttpRequest
are still somehow shadowed on the returned window object but I did notice at least evel(codeAbove).webkitRequestFileSystem()
available which is suspicious.
https://tinyurl.com/evel-bypass-01
I think this is somewhere between sandbox- and browser-bug. Technically, a fresh window should not be available in this scope.
Function('(0,eval)("this")')
Hey everyone,
I'm not sure how active this project is anymore since the last bypass was found, but I wanted to report a vulnerability anyway.
Here's how it works :
(1,eval)._global.alert('bypassing evel !')
Since (1,eval)()
is an an indirect call to eval, it can get a context and access the evel
object.
For more info about this type of calls you can check this awesome article :
http://perfectionkills.com/global-eval-what-are-the-options/#indirect_eval_call_theory
Consider this a question...
How can I implement dependencies and how can I make sure this doesn't open new vulnerabilities?
For example I have some custom Javascript classes and I want to use them during the evaluation of my code
. Just to give an example of what it could look like:
Person = function( FirstName, LastName, Age, Sex ){
this.FirstName = FirstName;
this.LastName = LastName;
this.Age = Age;
this.Sex = Sex;
};
Parent = function( FirstName, LastName, Age, Sex ){
Person.call( this, FirstName, LastName, Age, Sex );
this.Children = [];
};
Parent.prototype = Object.create( Person.prototype );
Father = function( FirstName, LastName, Age ){
Parent.call( this, FirstName, LastName, Age, "male" );
};
Father.prototype = Object.create( Parent.prototype );
Mother = function( FirstName, LastName, Age, Sex ){
Parent.call( this, FirstName, LastName, Age, "female" );
};
Mother.prototype = Object.create( Parent.prototype );
Child = function( Father, Mother, FirstName, Age, Sex ){
Person.call( this, FirstName, Father.LastName, Age, Sex );
this.Mother = Father;
this.Father = Father;
this.Age = Age;
this.addChildToFather();
this.addChildToMother();
};
Child.prototype = Object.create( Person.prototype );
Child.prototype.setParents = function(){
if( this.ObjectPlacement !== null ){
this.ObjectPlacement.PlacesObject.push( this );
}
};
Child.prototype.addChildToFather = function(){
if( this.Father !== null ){
this.Father.Children.push( this );
}
};
Child.prototype.addChildToMother = function(){
if( this.Mother !== null ){
this.Mother.Children.push( this );
}
};
So prototype etc. should be available...
Right now there's a difference between evel('XMLHttpRequest')
and evel('XMLHttpRequesy')
[sic] — the former returns undefined
while the latter throws a ReferenceError
.
I suspect the only way to do that would be to delete
every "local" variable, but that's not allowed in strict mode (and I'm not sure it'd work anyway).
BUT
If we can, somehow, it'd be cool!
c.f. http://perfectionkills.com/understanding-delete/#undeclared_assignments
Might be worth making clear that passing any sort of DOM object to the untrusted script is dangerous e.g. script injection via .innerHTML
and probably many more avenues…. (Although, how much will our iframe
mitigate of that?)
E.g. does the Reflect API (intro here: http://blog.keithcirkel.co.uk/metaprogramming-in-es6-part-2-reflect/) change anything? new.target
? Anything else?
"Readme says you could beat while(true) with an iframe, but I think iframes share the thread." — https://twitter.com/pfrazee/status/357566263893037056
Haven't fully researched/tested but this looks like it might be the case. E.g. https://github.com/timdream/simworker uses iframes to simulate webworkers, and documentation states "The script doesn't do the magic of taking the task background. Executions still block UI…"
There are a few non-standard extensions in the JS whitelist that probably don't need to be there (uneval
for one!)
I don't want to take too many "extras" away (e.g. TypedArrays) on the other hand it's safer to be conservative.
eval('1+1; 2+2;')
//=> 4
evel('1+1; 2+2;')
//=> SyntaxError: Unexpected token ;
Right now even if we took Object
off our global whitelist, an untrusted script could exploit its environment in most browsers via {}.__proto__.toString = function mayhem() {}
or whatnot.
Assuming the tricks I'm using do in fact otherwise block globals usage, there's an iframe trick available that would give each run of the script its own JS execution context.
@substack has just implemented a regexp filter that limits dangerous regexps.
https://github.com/substack/safe-regex
(we've also been discussing running restricted js for user leveldb queries)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.