The attack associated with poisoning the Array constructor is called JSON hijacking. It's an instance of a problem known as cross-site script inclusion (XSSI) that stems from the fact that browsers generally allow websites to include external JS files cross-origin.
Let's say, an authenticated user on your site can access some "secret tokens" at this API location:
https://yoursite.example/api/secret_tokens
And let's say the API response looks something like this:
tokens = ["secret123", "secrect456"]
This creates a vulnerability. An attacker could trivially exfiltrate the tokens of a logged-in user by sending them a link to evil.example
which would have code like this:
<script src="https://yoursite.example/api/secret_tokens"></script>
<script>
alert(tokens)
</script>
This way, the victim's browser is tricked into interpreting the API response as an external JS file. The API is inadvertently creating valid JS code (assigning the array to a global variable tokens
) and thereby leaking the tokens of a logged-in user to evil.example
.
Now, let's consider the more likely case that your API response looks similar to this:
["secret123", "secret456"]
This is still valid JS code, but since it's just an expression without assignments, an attacker can't access the content of the array as easily as above. The workaround here was to simply override the Array constructor:
Array = function() { foo = this; }
This particular trick was reported and fixed in Firefox in 2007 but since then, other techniques, e.g. employing Object.__defineSetter__
or Object.defineProperty
, have been found (and fixed).
How does EMCAScript 5 prevent that?
The ES5 specification demands that []
always uses the built-in Array
constructor (and the same applies to object constructors):
15.4.1.1
Array ( [ item1 [ , item2 [ , … ] ] ] )
Create and return a new Array object exactly as if the standard built-in constructor Array
was used in a new
expression with the same arguments (15.4.2).
Finally, let's consider the case that the API replies with a JSON object:
{"tokens": ["secret123", "secret456"]}
The simple reason why has never been vulnerable is that it's invalid JS: An isolated object in curly braces is parsed as a block instead of an object. So you can't include this JSON object as an external script without triggering an error. Historically, triggering a syntax error has been a convenient way to prevent XSSI attacks.
Here are some additional references to JSON Hijacking: