Giter Site home page Giter Site logo

evel's Introduction

evel.js

evel comes between eval and evil (code)

[see caveats below]

Usage

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

Caveats**

evel only works where ES5 strict mode does

In older browsers, evel will always throw an exception rather than running code.

JavaScript builtins still available

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.

The Halting Problem

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.

[** still] Unproven

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.

MIT license

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

Contributors

kumavis avatar natevw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

evel's Issues

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:

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.

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.

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.

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

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.

Oops?

Function('(0,eval)("this")')

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

Work in workers

Web workers have a different name for their global context. We should handle this in our strict-mode global finding case.

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

Sandbox-available evel.Function not correctly sanitized

The following code still breaks out of the sandbox:

Function.__proto__.constructor("return this")()

As surmised in #17, the fix in #15 merely shadows (rather than fully replacing) the original constructor property. Don't think this will be too hard to fix properly.

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.

c.f. browserify/vm-browserify#2

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.

infinite loops

while(true); or regexps that abuse lookahead to get exponential execution times?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.