Categorisation of ECMAScript(+event loops) Threats

David Bruant bruant.d at gmail.com
Thu Dec 6 03:35:52 PST 2012


Hi,

This post is not intentioned to be a perfect and final guide, but rather 
a conversation starter.
It's mostly triggered by what Andrea described as a potential attack 
(assigning Object.prototype.get).

# Description of the JS runtime

To a first approximation (ignoring things like alert/prompt and weird 
window resize event behaviors), a JavaScript runtime contains a heap, a 
call stack and a message queue. Messages accumulate in the queue and are 
treated one after the other. The time during which one message is 
treated is called a turn. Messages are queued when a user clicks and a 
listener listens or when a timeout fires, etc.
A message contains a function which, upon being called may call other 
functions (hence the call stack). Functions manipulate memory, including 
things in the heap. The heap contains a bunch of objects being 
accessible either from the global object or the scope of the function 
being called.

# Some properties

## Environment Mutability

Until ES5, pretty much everything in the heap could be modified at any 
turn by anyone; new properties could be added/removed from the global 
object, or the Object.prototype object for instance.
Sharing access to an object pretty much meant giving away the 
possibility to anyone with access to this object to mess up with it.
ES5 introduces a couple of constructs like Object.preventExtensions and 
Object.defineProperty(,,{configurable: false, writable: true|false}) to 
enable a programmer to share some object but have some properties 
enforced. It is probably the first time there are constructs that act on 
objects to create permanent effects on them.

## During a turn

Stating the obvious, but the function running in a turn has the power to 
do anything to whatever it has access to. Among the possible changes, 
it's possible to Object.freeze any object the function has access to.
When some JS code is running during a turn, it can't be preempted by 
some other code. The code has to end.

## First turn

There is a first turn.

## (Partial) conclusion

If malicious code runs, it can do whatever is possible during its turns. 
The role of a defenser will be to reduce "whatever is possible" to 
"nothing harmful".
First reaction, if the malicious code runs first, too bad, it can edit 
anything in the JavaScript environment it has access to, including 
modifying Object.prototype. The careful attacker will freeze 
Object.prototype after having modified it.
The opposite is true too. If the defender runs first, it can defend its 
environment. If the defender doesn't want Object.prototype to be 
modified, it can freeze it. If it wants to add polyfills then freeze 
everything, that's also a decent idea.
If during the first turn no one modifies anything in an harmful or 
defensive way, the decision to attack or defend is let to the code 
running in the next turn and so on.


# Categorisation of threats

## Threats that are unprotectable against

Programming languages or APIs sometimes leave holes by design that even 
the most skilled programmers couldn't write defensive programs against. 
If I recall, ES5 strict mode has gotten rid of all of them with the 
notable exception of mutable Date.prototype that provides a covert channel.
SES is an example of script that does everything it can to protect the 
JS environment (including fixing or working around almost-comformant 
environments bugs!)


## Threats that can be protected against

 From everything I have said, one clear thing is that protecting the 
JavaScript is an opt-in. If you do not opt-in to a secure environment, 
you're left to be a prey by default.
The list of potential attacks is probably endless.
Adding an Object.prototype.get function and waiting for people to use 
Object.defineProperty is such an attack. Putting an accessor on 
Array.prototype[0] is also a possibility.


# What does it mean for the language evolution?

The first category needs to be taken care of and is. Next features are 
carefully thought out to avoid unprotectable threats.
The second category is a bit more debatable. There is tension due to the 
fact that the vast majority of JS developers aren't aware that 
JavaScript security requires an opt-in like SES and as a consequence are 
victim of whatever buggy or malicious code they happen to add to their 
webpages.

Given that security requires an opt-in anyway, is it worthwhile to 
modify the language (like Object.defineProperty) to protect for some 
cases but not others?
I would tend to say no.

Regarding Andrea's specific case, I'd like to mention that proxy 
handlers are also based on [[Get]]. The inheritance is actually used in 
some cases for trap reuse (it was even more the case in the previous 
design, but still is the case). Should inheritance be prevented here too 
and ruin a valid use case? Just because an attacker might add an 
Object.prototype.someTrap and the defender might have forgotten to 
protect against it?
I don't think it's worth the cost in this case.

David


More information about the es-discuss mailing list