evel comes between eval and evil (code)
[see caveats below]
<script src="evel.js">
<script>
var result = evel("42");
console.log("Result: ", result);
</script>
<script>
var fn = evel.Function('test', "console.log("Hello ", test)");
fn("world"); // would work, but `fn` can't access `console` :-)
</script>
Basically, evel
providesCANNOT PROVIDE (see caveats/issue tracker) an evel
function that works like a eval
and a evel.Function
that works like Function
— except access to the global environment is somewhat prevented.
Load evel.js in a page a try out each of these lines in the JS console for funsies:
// regular `eval` allows access to shared prototypes
eval("({}).__proto__") === Object.prototype;
// `evel` doesn't
evel("({}).__proto__") === Object.prototype;
// this returns `undefined`
evel("eval('alert')");
// but this doesn't!
evel("eval")('alert');
In older browsers, evel
will always throw an exception rather than running code.
While evel
masks out all other globals, untrusted code will still have access to JavaScript builtins of a "clean" iframe. This should usually be fine, so long as leaking the user's local and current time is okay for your application, but if a poorly-written browser plugin/extension adds more functionality to JS core this could also be a concern.
Credit: Dominic Tarr
A malicious script could while(true);
and freeze the page. There's not a lot we could do about this while still allowing syncronous return values. This a denial of service attack: it doesn't directly give the attacker much, but it does break the user experience.
To avoid this, you could design your code to work asyncronously and maybe use evel
from within a communicating worker or something — but at that point consider using even more robust alternatives on the server-side if it makes sense for your application.
While I can't think of any other [new] ways to subvert it … maybe someone else will think of more? See the list of known bypasses in the issue tracker.
Copyright (c) 2013 Nathan Vander Wilt
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
evel's People
evel's Issues
Clean up whitelist
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.
Bypass 003: Recursive eval
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')
Bypass 003: Indirect eval call
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
Recap of recent progress
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:
- I suspect setting
evel.Function.constructor
simply shadows the originalObject.getPrototypeOf(evel.Function).constructor
and so this needs a more robust work. - similar for evel, we may need to clean up its chain better too (but c.f. discussion on #12)
- investigate: will the browser discrepancies noted in http://stackoverflow.com/questions/383172/correct-prototype-chain-for-function cause us any grief?
infinite loops
while(true);
or regexps that abuse lookahead to get exponential execution times?
Cannot evel multiple lines of code
eval('1+1; 2+2;')
//=> 4
evel('1+1; 2+2;')
//=> SyntaxError: Unexpected token ;
use safe-regexp
@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)
Sandbox-available evel.Function not correctly sanitized
DOM warning (documentation)
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?)
Dynamic import() is not blockable as a global
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
Bypass 001: IE10 using 1..constructor.constructor
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.
Use other depencencies in code
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...
Bypass 002: `Function("return this")()` d'oh!
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.
Documentation: using iframe may not avoid DoS
"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…"
Getter on `Object.prototype` gets global as `this` when accessed via variable lookup
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.
Oops?
Function('(0,eval)("this")')
Make masked globals completely gone? (i.e. ReferenceError)
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
Work in workers
Web workers have a different name for their global context. We should handle this in our strict-mode global finding case.
Is it possible to run evel inside a Worker?
Update globals list
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.
Other options / related work
Figured I'd open a thread to list "see alsos".
Review ES6 impact
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?
Allow caller to optimize re-runs
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.
Prevent untrusted code from modifying prototypes
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.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.