From Django documentation of their JsonResponse:

Before the 5th edition of ECMAScript it was possible to poison the JavaScript Array constructor. For this reason, Django does not allow passing non-dict objects to the JsonResponse constructor by default. However, most modern browsers implement EcmaScript 5 which removes this attack vector. Therefore it is possible to disable this security precaution.

How exactly can it be unsafe to send data from server to browser in the form of a JavaScript Array? How is such an attack called?

My experiments showed that while Django by default complains about sending to a browser something like this: ["foo", "bar"], it does not complain about sending something like this: {"foo": ["bar", "baz"]} How can it be any safer to send an array wrapped in an object than sending a "raw" array?

How does EMCAScript 5 prevent that?

  • afaik, only FF1.5 has this vulnerability; and it was fixed years before ES5. Sending data was not the issue, the problem was if someone re-defined the waiting client's browser's window.Array with a custom function that "tee"d the array contents to another function when an array was constructed via a literal. Since the Object literal form on it's own is a syntax error, it doesn't have the same issue. Browsers can now use CORS to fetch JSON via "ajax", and that doesn't interpret the response as JS code, as the exploit required. – dandavis May 16 '17 at 13:16
  • ES5 officially added the JSON.parse() routine which is used instead of eval() to parse JSON. – dandavis May 16 '17 at 13:22
  • @dandavis based on answer, that sounds irrelevant for the attack – Nick T Oct 10 '17 at 21:29

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:


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>

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

  • You say: The simple reason why this isn't vulnerable is that it's invalid JS. But, I bet it is valid JSON? Since, barring some subtleties regarding permitted characters in strings, JSON is a subset of JavaScript, how can valid JSON be invalid JavaScript at the same time? – gaazkam May 16 '17 at 13:29
  • 2
    @gaazkam <script>{"abc":123}</script> is invalid syntax, because JS thinks this is a block (enclosed in {}). However, this would be valid: a = {"abc":123}. It's a syntax quirk. – Arminius May 16 '17 at 13:33
  • So, it's safe just by pure luck – Roberto Maldonado Aug 31 '18 at 8:10
  • Well , we have a debate here . care to explain why {a:1} was hacked if it's not an object ? – Royi Namir Mar 17 '19 at 13:39
  • {a:1}: {} opens/closes a block, while a:1 is a labelled number literal. Labels are only useful in nested loops and nested switch statements outerLoop: for (...) { for (...) { break outerLoop } }. So {a:1} is functionally equivalent to {1}, which is functionally equivalent to 1. – user3654410 Dec 9 '20 at 12:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.