-1

I know JSON is supposed to be a notation that can otherwise function as direct-code to evaluate Javascript? But is that so?

There is a function I know about and use when I must, JSON.parse. It's the inverse of JSON.stringify. But is it otherwise safe to eval,

'let a = ' + JSON.stringify( obj ) + ';'

Is there any case where the above would leave you vulnerable? Assuming both are valid results of JSON.stringify is there any difference in security between,

eval('let a = ' + JSON.stringify( obj ) + ';'

let a = JSON.parse( JSON.stringify( obj ) );
Evan Carroll
  • 2,325
  • 4
  • 22
  • 29
  • 1
    Note that `eval` should be spelled with an `i`. That aside, why would you want to use eval when it can be avoided? Also, in your specific example, you could just `let a = obj`, so I assume that in reality the JSON comes from "elsewhere". If that "elsewhere" is even remotely possible to get out of control, don't use eval – Hagen von Eitzen Jan 05 '19 at 22:11

2 Answers2

3

If it were safe to use eval(), there would be no need for a special function to parse JSON data.

Put a bit differently, try feeding the following to eval() and your JSON parsing function, and note the difference in what happens:

0; for (var i = 0; i < 10; i++) {console.log(i)};

This obviously isn't valid JSON, so the JSON parsing function will of course reject it as invalid. The eval() function, however, will do exactly as this perfectly valid JavaScript code says to, and log the numbers 0 through 9 to the JavaScript console (note that the 0; at the beginning defuses syntax errors that would be caused by constructs such as your example usage of eval()). Given this, it's pretty easy to see that eval() is dangerous if you don't at least check that the data is valid JSON.

This, combined with the simple fact that you can never trust 100% that data you are receiving is actually in the format the source claims it is and is a valid instance of that format should be enough to demonstrate that just a bare eval() call is not a safe way to parse JSON.

You might consider doing some simple checks and just call eval()` on the data anyway, but can you be certain that you thought of everything? By the point you get reasonably sure that you did, you've usually written about 95% of a JSON parser already, and it's just simpler (and quicker) to parse the data while you're validating it.

There are a couple of other here though that aren't exactly security problems so much as usability problems:

  • eval() usually does not give very friendly errors when it fails to parse JSON data, largely because most JavaScript implementations give particularly poor errors when they fail to parse an object literal properly. In some cases, it may not even give any errors at all, because the data you're trying to read might be perfectly valid JavaScript code even though it's not valid JSON.
  • Standards compliant JSON is not automatically standards compliant JavaScript. The JSON standard allows for a handful of Unicode characters to be unescaped in strings which JavaScript requires to be escaped. JSON with any of those characters un-escaped in a string will parse correctly on most JSON parsers, but not when fed to eval().
  • You also have to trust that the JSON document you are parsing was properly created in the first place. Aside from the above-mentioned issue with certain characters needing to be escaped inside strings in JSON, there are a couple of other edge-cases that some JSON generators don't properly account for (for example, Unicode characters above code-point U+FFFF must be escaped using UTF-16 surrogate pairs in JSON, no matter what encoding the data is actually transferred in).
Austin Hemmelgarn
  • 1,625
  • 7
  • 9
  • It's a really well written post, but I don't think it follows from *"Assuming both are valid results of `JSON.stringify` is there any difference in security between"* That is to say, no implementation of `JSON.stringify` will produce `0; for (var i = 0; i < 10; i++) {console.log(i)};` I would be very clear about the limits to this answer as having violated the constructs of the question, but have an upvote for taking the time and being right. – Evan Carroll Jan 23 '19 at 05:11
  • @EvanCarroll The reason I put that qualifier on it is that while pretty much no JSON generator is going to spit out obviously dangerous JSON data, quite a few spit out subtly broken JSON, and the way that `eval()` and `JSON.parse()` handle those cases is different (t minimum, you get different exceptions out of them). It's not exactly a security difference (usually, I've seen JSON that will lock up an `eval()` parse attempt but error out correctly for `JSON.parse()`), but it's still a pretty important difference. – Austin Hemmelgarn Jan 23 '19 at 14:26
2

Technically speaking, yes this would be safe because a JSON string cannot contain code, because according to the spec, JSON can only contain object, array, string, number, true, false, and null data, not any other types like functions.

eval('let a = ' + JSON.stringify( obj ) + ';');

That is assuming JSON.stringify is implemented properly, and cannot be tampered with.

The only way JS code could be included in a JSON object would be inside a string, which would require an explicit eval on that string's value after parsing the JSON, which is no different from using a proper JSON parser.

I can't think of any good reason to use eval for this purpose though, given that I'm not aware of any platforms that implement JSON.stringify and not JSON.parse and invoking the JIT compiler just to parse some JSON would probably have bad performance.

Evan Carroll
  • 2,325
  • 4
  • 22
  • 29
Alexander O'Mara
  • 8,774
  • 6
  • 34
  • 38
  • 1
    The interesting part of your answer is _"because a JSON string cannot contain code"_, but you don't really explain it. Can you either provide a citation or explain your reasoning? Naively, I would think that it's possibly to construct a JSON object such that its string representation will break out of that eval - for example, something to the effect of `{"a":"1';doSomethingBad();"}` so that `doSomethingBad()` ends up getting executed. – Mike Ounsworth Jan 06 '19 at 00:59
  • @MikeOunsworth the issue there is that JSON.stringify escape the `'` in your example. or, should. – Evan Carroll Jan 06 '19 at 06:51