Anatomy of a Subtle JSON Vulnerability

asp.net, code 0 comments suggest edit

I recently learned about a very subtle potential security flaw when using JSON. While subtle, it was successfully demonstrated against GMail a while back. The post, JSON is not as safe as people think it is, covers it well, but I thought I’d provide step-by-step coverage to help make it clear how the exploit works.

The exploit combines Cross Site Request Forgery (CSRF) with a JSON Array hack allowing an evil site to grab sensitive user data from an unsuspecting user. The hack involves redefining the Array constructor, which is totally legal in Javascript.

Let’s walk through the attack step by step. Imagine that you’re logged in to a trusted site. The site makes use of JavaScript which makes GET requests to a JSON service:

GET: /demos/secret-info.json

that returns some sensitive information:

["Philha", "my-confession-to-crimes", 7423.42]

Now you need to be logged in to get this data. If you go to a fresh browser and type in the URL to /demos/secret-info.json, you’ll get redirected to a login page (in my demo, that’s not the case. You’ll have to trust me on this).

But now suppose you accidentally visit evil.com and it has the following scripts in the <head /> section. Notice the second script references the JSON service on the good site.


<script type="text/javascript">
var secrets;

Array = function() {
  secrets = this;
};
</script>

<script src="https://haacked.com/demos/secret-info.json" 
  type="text/javascript"></script>

<script type="text/javascript">

  var yourData = '';
  var i = -1;
  while(secrets[++i]) {
    yourData += secrets[i] + ' ';
  }

  alert('I stole your data: ' + yourData);
</script>

When you visit the page, you will see the following alert dialog…

evil alert message

…which indicates that the site was able to steal your data.

How does this work?

There are two key parts to this attack. The first is that although browsers stop you from being able to make cross-domain HTTP requests via JavaScript, you can still use the src attribute of a script tag to reference a script in another domain and the browser will make a request and load that script.

The worst part of this is that the request for that script file is being made by your browser with your credentials. If your session on that site is still valid, the request will succeed and now your sensitive information is being loaded into your browser as a script.

That might not seem like a problem at this point. So what if the data was loaded into the browser. The browser is on your machine and a JSON response is not typically valid as the source for a JavaScript file. For example, if the response was…

{"d": ["Philha", "my-confession-to-crimes", 7423.42]}

…pointing a script tag to that response would cause an error in the browser. So how’s the evil guy going to get the data from my browser to his site?

Well It turns out that returning a JSON array is valid as the source for a JavaScript script tag. But the array isn’t assigned to anything, so it would evaluate and then get discarded, right?. What’s the big deal?

That’s where the second part of this attack comes into play.

var secrets;
Array = function() {
  secrets = this;
};

JavaScript allows us to redefine the Array constructor. In the evil script above, we redefine the array constructor and assign the array to a global variable we defined. Now we have access to the data in the array and can send it to our evil site.

In the sample I posted above, I just wrote out an alert. But it would be very easy for me to simply document.write a 1 pixel image tag where the URL contains all the data in the JSON response.

Mitigations

One common mitigation is to make sure that your JSON service always returns its response as a non-array JSON object. For example, with ASP.NET Ajax script services, they always append a “d” property to the response, just like I demonstrated above. This is described in detail in this quickstart:

The ASP.NET AJAX library uses the “d” parameter formatting for JSON data. This forces the data in the example to appear in the following form:

{“d” : [“bankaccountnumber”, “$1234.56”] }

Because this is not a valid JavaScript statement, it cannot be parsed and instantiated as a new object in JavaScript. This therefore prevents the cross-site scripting attack from accessing data from AJAX JSON services on other domains.

The Microsoft Ajax client libraries automatically strip the “d” out, but other client libraries, such as JQuery, would have to take the “d” property into account when using such services.

Another potential mitigation, one that ASP.NET Ajax services do by default too, is to only allow POST requests to retrieve sensitive JSON. Since the script tag will only issue a GET request, a JSON service that only responds to POST requests would not be susceptible to this attack, as far as I know.

For those that keep track, this is why I asked on Twitter recently how many use GET requests to a JSON endpoint.

How bad is this?

It seems like this could be extremely bad as not many people know about this vulnerability. After all, if GMail was successfully exploited via this vulnerability, who else is vulnerable?

The good news is that it seems to me that most modern browsers are not affected by this. I have a URL you can click on to demonstrate the exploit, but you have to use FireFox 2.0 or earlier to get the exploit to work. It didn’t work with IE 6, 7, 8, FireFox 3 nor Google Chrome.

Take this all with a grain of salt of course because there may be a more sophisticated version of this exploit that does work with modern browsers.

So the question I leave to you, dear reader, is given all this, is it acceptable to you for a JSON service containing sensitive data to require a POST request to obtain that data, or would that inspire righteous RESTafarian rage?

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

44 responses

  1. Avatar for Scott
    Scott November 20th, 2008

    That's actually a variant of an exploit that has been around for a while. It's kind of odd that GMail would have gotten hit by it.
    www.lazycoder.com/.../ajax-library-security-adv...

  2. Avatar for Graeme Christie
    Graeme Christie November 20th, 2008

    Please no more dirty coding hacks or workarounds to deal with the ineptitude of HTML and javascript as an application platform. Either fix the platform, or better yet, put Silverlight, Flash (and *smirk* Java FX) into a CAGE DEATHMATCH until ONE comes out alive and we'll throw the whole HTML/Javascript/Ajax mess into an industrial strength car crusher (and if, while standing by looking on, Internet Explorer suspiciously trips and falls to it's death then so be it) and get on with our lives.

  3. Avatar for Mike Amundsen
    Mike Amundsen November 20th, 2008

    interesting post. yes, JSON is vulerable, but not in any way that is new. it's pretty easy for me to send sensitive data via XML, plain/text, etc. the patterns adopted by ASP.NET Ajax ("d:" and POST-only) are hurdles to ismple hacking, but they end up reducing useful interactions w/ other servers and clients not participating in these 'hacks.'

  4. Avatar for haacked
    haacked November 20th, 2008

    @Mike well the "d:" hack still abides by JSON standards. The only issue is now the JSON you get returned has a property named "d" which contains your data.
    @Scott I think the GMail hack was a while ago, back in 2006. I just learned about it now.

  5. Avatar for Mike Amundsen
    Mike Amundsen November 20th, 2008

    yep - the "d:" is a minor deal. i'm just a bit grumpy today since i've been working w/ the Astoria serializations of SDS and found the "d" a bit 'crufty.' no matter. we're cool.

  6. Avatar for cowgaR
    cowgaR November 20th, 2008

    thanks Phill for nice info (I wonder how are modern browsers stopping this attack), I've just read a security article about various web-attacks and this one was among them...this is but more to the point.
    one question though, the attack only concerns "sensitive" data of particular (one) JSON request... so to say on most pages (that just show basic info) stealing the data wouldn't mean any harm, would it?
    I mean things like credit cards info/goods price/private messages and things like that that exist, those are sensitive but I would not send them via JSON in the first place.
    otherwise I am free to use JSON and AJAX on my page as I want, steal freely ;)

  7. Avatar for Richard
    Richard November 20th, 2008

    My suggestion to combat this (requires a bit of work obviously) is to encode a request key into every JSON url, like a one-time scratch pad. Perhaps an internal counter with a random starting seed that's SHA'd into the URL. Each JSON request would have to provide the next key to use, etc. Any invalid key would refuse access to the data.
    URL example: http://haacked.com/demos/secret-info.json#f4c59b1e

  8. Avatar for Douglas Meyer
    Douglas Meyer November 20th, 2008

    This doesn't actually seem to be a vulnerability in JSON. It does show that JSON makes it easier to get what a Cross-site scripting attack.

  9. Avatar for haacked
    haacked November 20th, 2008

    @Richard You are correct in that probably 99.9% of JSON services are unaffected. Not only would the service have to contain sensitive data for it to be a worry, but it would have to send the data as a pure array and not as a true JSON object. On top of that, it would have to allow for GET requests of that data.
    However, it did hit GMail back in 2006. By now, I think it's *almost* a non-issue as far as I can tell, only FF 2.0 and earlier is succeptible. However, I could be totally wrong about that. You never know what other hijinks you can do with javascript. ;)

  10. Avatar for haacked
    haacked November 20th, 2008

    @Douglas technically, it's not a vulnerability *in* JSON. It's more of an issue with *using* JSON. If we could go back in time and redesign JSON, we'd probably make it such that the array would not be a valid javascript.
    And it's really more of a CSRF attack combined with a reverse XSS attack. I say reverse XSS because in this case, you don't have to inject any scripts onto the good site. You just make a request for the good site's script and run it on evil.com. Which is the opposite of the typical XSS attack. ;)

  11. Avatar for Chris Dary
    Chris Dary November 20th, 2008


    {"d" : "bankaccountnumber", "$1234.56" }

    Are you missing some brackets there or is that meant to be invalid JSON?

  12. Avatar for Chuck
    Chuck November 20th, 2008

    Other useful sources from when this discussion surfaced a while back:
    yuiblog.com/.../json-and-browser-security/
    developer.yahoo.com/.../#requestsignatures

  13. Avatar for Paolo Bonzini
    Paolo Bonzini November 21st, 2008
    we'd probably make it such that the array would not be a valid javascript.


    I'd say actually, you'd probably make it such that returning just an array would not be a valid JSON. The point of JSON is exactly that a simple regex match + eval is enough to get the data (though people now are using full-fledged parsers more often).

  14. Avatar for Riaz Missaghi
    Riaz Missaghi November 22nd, 2008

    Checking the refferer of a request should prevent this attack, even if refferers can be masqueraded, this attack must be run from an unsuspecting victims browser so there is no chance them altering the request packet to try to fake the request source. Great article, thanks!

  15. Avatar for Todd
    Todd November 23rd, 2008

    This is exactly why I always log out and clear my cookies after visiting any secure site (bank, 401k, etc).

  16. Avatar for Richard Kimber
    Richard Kimber April 13th, 2010

    This is the first time I have truly understood the need for the "d", and from now on, will not be without it.
    I don't like the idea of using a POST to GET data though. Right tool for the right job and all that.
    Rich

  17. Avatar for ravi
    ravi November 16th, 2010

    Nice post. But if we fix the CSRF issue, i don't see any way JSON response will be vulnerable.

  18. Avatar for Bob Jones
    Bob Jones November 29th, 2010

    I have been struggling to get JQueryUI AutoComplete working with an ASMX web service. Yesterday, I got the round trip working and discovered the problem you describe in this article. I previously discovered that GET needed to be POST and a bunch of other small issues, but now I am trying to go the final mile to code-complete and I am stuck with the "d..." issue. I am not a JavaScript programmer, so I am not clear how to work around this.
    Can you please post the last piece that shows how to remove the "d" prefix and restore the string to its proper state?
    Thanks...

  19. Avatar for Lawrence Wang
    Lawrence Wang September 20th, 2011

    "I'd say actually, you'd probably make it such that returning just an array would not be a valid JSON."
    +1

  20. Avatar for Andrea
    Andrea June 16th, 2012

    is it safe to use encryption in javascript?
    http://cryptojs.altervista.org/secretkey.html

  21. Avatar for Programaths
    Programaths June 29th, 2012

    Funnily, I'm not impacted by this. I always format JSON like this :

    {
    "data":'any valid json here',
    "error":'null or object containing code and reason'
    }

    Using array makes may decrease your load and bandwidth but it increase the maintenance complexity.
    I hear : "Yeah man, that's not standard way of doing stuffs".
    For them : look at JSON RPC ;)
    Waiting patiently for Websockets !

  22. Avatar for Saeed Neamati
    Saeed Neamati September 15th, 2012

    Dear Phill, please correct me if I'm wrong, but JavaScript arrays are not JSON. I mean, JSON stands for JavaScript Object Notation (yeah, I known you know it, just emphasis ;)). So, it's literally a notation for JavaScript objects, not JavaScript arrays. In other words, JSON should start with {, not [.

  23. Avatar for haacked
    haacked September 17th, 2012

    @Saeed, I shall correct you then. :)
    Here's a quote from the JSON spec:

    An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).


    Thus if you send a JavaScript array it will be interpreted as valid JSON. Don't take my word for it. Try it yourself!

  24. Avatar for edinella
    edinella January 11th, 2013

    What if the server only answer requests containing the header "X-Requested-With"? So we make sure that the information does not leave the server when using a tag <script src="...">.

  25. Avatar for edinella
    edinella January 11th, 2013

    Check if the request was issued with the "X-Requested-With" header field set to "XMLHttpRequest" (jQuery etc).
    http://expressjs.com/api.ht...

  26. Avatar for haacked
    haacked January 11th, 2013

    That helps, but chances are you might want that endpoint accessible via clients other than XMLHttpRequest. However, it probably wouldn't hurt to put such an enforcement in and document that other clients need to add this header to requests.

  27. Avatar for dobes_vandermeer
    dobes_vandermeer August 13th, 2013

    It's meant to be invalid, so you have to fetch it as a string, strip out the "d" :, and then parse it. This prevents the data from being loaded as a script tag.

  28. Avatar for Satya
    Satya November 21st, 2013

    I noticed same issues , while making REST api calls using some secret keys . But may be much better to make header request calls via backend .

  29. Avatar for James Sanders
    James Sanders December 15th, 2013

    The specification of ES5 has language that disallows this: "Let array be the result of creating a new object as if by the expression new Array() where Array is the standard built-in constructor with that name." (http://www.ecma-internation... page 64). It looks like modern browsers are mostly ES5 compliant (http://kangax.github.io/es5..., though I haven't found any data on compliance with that specific part of the spec.

  30. Avatar for Erik Eckhardt
    Erik Eckhardt June 5th, 2014

    It wasn't helpful that this post gives conflicting information:

    {"d": ["Philha", "my-confession-to-crimes", 7423.42]}
    {"d" : "bankaccountnumber", "\$1234.56" }

    Nor was it helpful that another commenter, rightly asking about the second piece of code, was given incorrect information. It really needs to be spelled out better what the actual fix is and its exact mechanism.

  31. Avatar for Jack
    Jack November 24th, 2014

    Great post! demystified a lot of myths about d! I was looking to disable it now i advocate that!

  32. Avatar for marcospgp
    marcospgp October 8th, 2015

    Another reason why stateless authentication is the future!

  33. Avatar for lxgr
    lxgr October 15th, 2015

    Note that while this code is invalid as (top-level) JavaScript, it is valid when parsed as JSON.

  34. Avatar for josue
    josue January 8th, 2016

    jajaja Comentario de hace 7 años!!
    pensara lo mismo???

  35. Avatar for Pankaj Yadav
    Pankaj Yadav March 8th, 2016

    We have a single page application(knockoutjs and web api), in which we are getting(HttpGet) the data from server via Asp.net Web Api. These data are visible in network of browser in json format.
    How can i prevent those record so that malicious user can't change those data???

  36. Avatar for Noman Ur Rehman
    Noman Ur Rehman March 8th, 2016

    I am a bit puzzled at how this response {“d” : [“bankaccountnumber”, “$1234.56”] } would solve the problem?

    Is it because you can override the constructor in case of [ ] but not in case of { }?

    Can't I still do this for an attack?


    var secrets;
    Array = function() {
    secrets = this.d;
    };
  37. Avatar for haacked
    haacked March 8th, 2016

    As I mentioned in the post, "Well It turns out that returning a JSON array is valid as the source for a JavaScript script tag"

    Consequently, the response {"d" :["..."]} is not valid JSON so a browser won't execute that if it's the response to a SCRIPT tag.

  38. Avatar for Simon
    Simon June 17th, 2016

    Use HTTPS

  39. Avatar for Sagiv Frankel
    Sagiv Frankel December 19th, 2016

    Hey there,
    I've tried to test this out and the problem doesn't seem to occur.
    using [] doesn't call the Array constructor. Am I missing something?

  40. Avatar for haacked
    haacked January 2nd, 2017

    I don't think any modern browsers have this flaw anymore.

  41. Avatar for Sagiv Frankel
    Sagiv Frankel January 3rd, 2017

    Thanks, good to hear - tough it was an interesting hack...

  42. Avatar for TheFuture
    TheFuture September 12th, 2017

    Hello from 9 years into the future! All of these suggested replacement frameworks are either dead or dying.

  43. Avatar for eshansingh1 .
    eshansingh1 . January 8th, 2018

    Yes, but that's because the front-end dev ecosystem is completely retarded and we didn't throw out the horrible, poorly-thought-out inflexible mess that is JavaScript.

  44. Avatar for ChrisMonahan
    ChrisMonahan March 22nd, 2018

    The irony now looking back at someone suggesting 9 years ago that we use Silverlight(!?) or Flash(!?) as a robust replacement for HTML/Javascript

    *smirk*