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