These attacks are essentially standard Cross Domain Request Forgery attacks, but they exploit two special aspects with how browsers handle JavaScript requests:

  1. They are executed in the context of the calling page
  2. They are not subject the "Same Origin Policy" of AJAX requests.

The first point makes JavaScript resources different to images and stylesheets, which are not executed, and therefore are mostly opaque to the calling page. (There is a small amount of information that can be leaked from the loading these kinds of resources, the JavaScript execution environment provides far more) The second point differentiates JavaScript from AJAX requests, which are entirely transparent, but only if they were made against the same domain as the containing page.

So what do these requests look like? Let's start with the most trivial example.

Attack 1

Imagine that you are writing a rich web application that holds some information about your users. A lot of your application is written in JavaScript, and in order to include the user's personal information in the pages, you dynamically generate some of the JavaScript. So you end up with a JavaScript resource like this:

user_data = { name: "John Smith", email: "john.smith@example.com" } ;

function render_page() { 
 // ...
}

and include it in your HTML file as:

<script type="text/javascript" src="app.js"></script>

That all appears to be secure - the "app.js" resource is protected behind your login page and your session management follows good security practices, so what could go wrong?

Well, this script is vulnerable to the most rudimentary form of Cross Site Request Forgery. An attacker can create a page like this:

<script type="text/javascript" src="http://www.yourdomain.com/app.js"></script>
<script type="text/javascript">
$( function()
{
  if(user_data)
  {
    $("#email").val(user_data.email);
    $("#name").val(user_data.name);
    $("#form").submit();
  }
}
</script>

Then, if anyone navigates to the attacker's page, while they are logged into your app, then the attacker can obtain their name and email address, which will allow them to do a fairly well targeted phishing attack. And if your application contains more private data than that, then it's even worse.

But JavaScript hijacking can be even worse than that. Because a lot of web applications now deal in JSON formatted data, and JSON is (essentially) a subset of JavaScript, there are a lot of more complex requests that have the potential to open up a JavaScript hijacking vulnerability.

Attack 2

If a piece of JSON data is an array literal, such as ["John Smith", "john.smith@example.com"] or [ {name:"John Smith", email:"john.smith@example.com"} ], then it is also a valid JavaScript <script> and can be embedded inside the attackers page.

There's a bit of trickery involved in getting access to that data, but it's all described in the original JavaScript hijacking paper. The end result is that if your application uses private JSON with arrays as the top level data type, then you're almost certainly vulnerable to JavaScript hijacking attacks.

For example, if you have an AJAX request that returns a list of "friends" as a JSON array:

[
   { nickname: "John", fullname: "John Smith", page: "/view-user.php?id=14595" },
   { nickname: "Sally", fullname: "Sally Jones", page: "/view-user.php?id=19412" },
   { nickname: "Peter", fullname: "Peter Wong", page: "/view-user.php?id=10573" }
]

Then an attacker can perform a very similar attack to the email address harvesting (attack 1, above) to get a user's friends list.

This form of attack is only relevant for JSON array literals, because a standalone Object literal is not valid JavaScript, so it cannot be loaded by a <script> tag.

Attack 3

The final angle on JavaScript hijacking is the most dangerous, but thankfully is the least common - transparent cross domain request forgery.

Most CDRF attacks are blind - the attacking page makes a call into your application to initiate some nefarious action, but cannot determine whether the request was successful, due to the Same Origin Policy enforced by the browser. However, as we've shown a <script> tag is not subject to Same Origin policies, so an attacker can determine whether the request was successful, and adjust their attack, or they can orchestrate multiple requests and feed information between them.

Because traditional CDRF attacks are blind, some countermeasures are based on the belief that all CDRF attacks will be blind, but in the presence of JavaScript / JSON based requests, they may not be. For example, the use of a session (or request) token can help prevent CDRF attacks, but if the token can be read from a vulnerable JSON message, then the protection it offers is dramatically reduced.

Avoiding JavaScript Hijacking vulnerabilities

This is supposed to be an architecture blog, so let's get down to business - what architectural decisions are relevant to the prevention of JavaScript hijacking attacks?

Some basics:

  • Avoid dynamic generation of JavaScript resources. As developers move from working on applications that performed most of the work server-side into more rich applications with large amounts of JavaScript, there's a temptation to carry some of the traditional techniques across, and in some cases this leads to developers dynamically generating some parts of their JavaScript (e.g. in PHP or a templating engine like FreeMarker).
    This should be avoided. Dynamically generating JavaScript resources is not fundamentally wrong, but it opens up your application to the potential for attacks like #1, and they can be difficult to find (and avoid) without code reviews and auditing. It's far easier to simply force all resources that are served up as JavaScript files to be pure static content.
    If you need to do something like the "user_data" in the example in attack 1, then embed it inside your HTML page.
  • Avoid rendering arrays as the top level element in JSON resources. Ideally all your JSON should be being served up from 1 place and you can put a check inside that piece of your application to prevent any arrays being rendered at the top level element. The most brute force solution is to put a filter around your application to check for (and prevent) JSON responses where the first non-whitespace element is a '['
  • Some people recommend manipulating all JSON responses to that they cannot be treated as JavaScript - e.g. enclosing them in comments, or starting the response with while(true); That can be a reasonable approach, but it reduces the interoperability of your JSON data - so you need to think about who is consuming the JSON, and whether they need true JSON, or whether they can handle manipulated JSON.
    Most JSON parsing libraries do not handle these comments/while-loops automatically, so if you use them, you will need to include your own de-manipulator on the parsing side. You should be able to provide suitable examples for your JavaScript toolkit if you search the web.
  • Evaluate your CDRF controls and countermeasures with a view to how they perform for transparent CDRF attacks (like Javascript Hijacking). Controls which rely on attacks being blind are likely to fail to prevent JavaScript Hijacking attacks.
  • More generally, this shows (once again) that our threats and our controls need to be thoroughly re-evaluated when we make fundamental paradigm changes. When we moved from client/server apps to web-apps, we opened ourselves to a new class of attacks such as cross site scripting. Now, as we're continuing to move into more rich web-apps, we're opening up new threat vectors that need to be analysed and controlled. Any change in your underlying application architecture will bring corresponding changes to your threat model, and therefore necessitate equivalent changes in your security controls.

    On the JSON side, these attacks show why the decision to design a data transport format around an executable language syntax is fraught with danger. We're stuck with JSON now, and this is not necessarily a reason to avoid it, but I certainly consider it sufficient evidence to act as a warning to people not to go down that path again - data transport formats and execution languages should be independent.