Using JSONP for cross domain requests

It is often seen that developers are not confined to the limits of their own domains. When you make requests through JavaScript across domains, the browser prevents the request from going through citing the absence of an ‘Access-Control-Allow-Origin’ header. This is termed as the ‘Same Origin Policy’ of browsers which allows scripts running on a domain to make requests to resources on the same domain only, comprising the same URI scheme, domain and host number. There are many ways around the same origin policy- ranging from routing the request through a web proxy to using CORS (Cross Origin Resource Sharing), but the most popular method is using JSONP.

What is JSONP?

JSONP simply refers to “JSON with padding”. It is essentially a JSON response wrapped around a callback function that is specified in the URL. For instance, the following a JSON response.

{ “username”: “sdaityari”, “name”: “Shaumik Daityari”}

The same response with a callback function specified as processData is as follows.

 processData({ “username”: “sdaityari”, “name”: “Shaumik Daityari”})

How does JSONP help in working around the same origin policy?

As browsers don’t allow requests to other domains, how then do we add external files to CDNs (Content Delivery Networks) to speed up page loading and still get them to work? The hidden agenda is here is the fact that these files are present under the src attribute of <script> tags. This leads to a conclusion that anything under the <script> tags is executed by the browser under the context of the current domain!

Using the same idea, we supply a callback function, generally as a GET variable, to the src in the <script> tag, and we get a response of a JSON wrapped with the callback function. That essentially means that the callback function is executed with the JSON response as arguments. That helps in working around applications just like we did in the case of AJAX.

In the JSONP example provided, we would execute the function like the following-

 <script src=”http://www.example.com/json_data?callback=processData“></script>

By doing so, processData would be executed with the given arguments.

Why would this not work if it was returning just JSON?

In place of a JSON response padded within a function, if the server just returned a JSON, the data would not get executed, instead raising a Syntax Error. You could emulate a response by pasting some JSON into your JavaScript console.

When can it go wrong?

In the example above, the data that was returned through JSON was not so sensitive. It just contained the username and name. However, imagine an ecommerce site which stores credit card details as a part of your profile. Let’s assume the following request being made-

<script src=”http://api.myecommercesite.com/profile?callback=processData”></script>

The website api.myecommercesite.com would return the following response irrecpective of the website that requested the information.

 processData({

   “name”: “Shaumik Daityari”,

   “card_no”: “xxxx xxxx xxxx xxxx”,

   “expiry_date”: “xx-xxxx”

 });

How does an attacker use it to get your data?

In the ideal case, this data is received by the intended website and used accordingly. However, let’s say that a malicious site, www.attacker.com, gets wind of the information and tricks you into redirecting you to their server.

Basically, you are browsing www.attacker.com and you are asked to click on something. Their server then sends the same response and since you are logged into the ecommerce site, data containing your information is returned. (There are other non-JSONP related security checks which can prevent this from happening, but let’s assume there were no other security measures to prevent this from happening.)

Once a malicious site gets hold of the sensitive data, it can process the data on the context of the site, and therefore do whatsoever it wishes with the data, most probably storing it in their own servers for later use. Not only this, a malicious site can also get hold of your cookies which contain vital information that a website uses to track your progress on its site.

Using JSONP safely

The reason JSONP got so popular is the ease of use and implementation. All you need is a callback and you are done. Therefore, there are many security concerns which need to be taken care of while using this technique.

Sanitize callback

This is one little thing that can lead to dangerous consequences. In fact, many tutorials talking about the security in the JSONP method fail to get this one right. In PHP, you would generally execute the following.

 echo $_GET[“callback”] . “(“ . json_encode($my_data) . “);”;

In addition to that, vulnerabilities in JSONP have also been identified through a term called flash injection.

The right way, as explained by Dylan Tack on his blog, is to use appropriate headers to manipulate the output in case the callback is being used for an XSS attack. He uses the following code-

function generate_jsonp($data) {

 if (preg_match(‘/\W/’, $_GET[‘callback’])) {

   // if $_GET[‘callback’] contains a non-word character,

   // this could be an XSS attack.

   header(‘HTTP/1.1 400 Bad Request’);

   exit();

 }

 header(‘Content-type: application/javascript; charset=utf-8’);

 print sprintf(‘%s(%s);’, $_GET[‘callback’], json_encode($data));

}

Full trust on a different domain

Using the JSONP requires that you trust the remote domain fully. This essentially means that if, for some reason, the functionality remote domain breaks, your service breaks too. It remains your decision, however, whether you want to depend on a third party service.

Moreover, as we are using it under script tags, it is difficult to catch errors within it and error handling changes from browser to browser, making it difficult to manage a proper structure.

User Authentication

For argument’s sake, a possible way to login a user into a remote site using only JSONP would involve sending the username and password as GET variables (since that is the only way HTTP requests can get you the data in a script tag). It is an unsafe method of authentication and therefore, should be avoided.

For the purpose of user authentication, it’s favourable that you follow the general workflow of OAuth- redirect to parent website, authenticate the user and on successful authentication, generate and share a token.

Using CSRF tokens for write operations

In case you are using the JSONP technique to write data to your server (whether it’s create or update), you must know that JSONP uses GET, which is not secure. In order to make sure that everything goes according to plan, you could issue a token within the headers of every request. A token needs to be generated for every user who is authenticated using the step above.

That being said, there are far better options considering security during writes, updates or deletes and you should follow them rather than finding workarounds with JSONP, which should ideally be used for reads only.

Looking forward- using CORS (Cross Origin Resource Sharing)

We have seen a few use cases of JSONP and all of them can be achieved by the web proxy method too. Although the JSONP technique remains popular, the vulnerabilities in it make it a headache to implement in complex situations. CORS has been gaining popularity steadily as its support in major browsers continues to grow, and it’s taking over the uses of JSONP.

   request = new XDomainRequest();

   request.open(method, url);

   request.onload = function() {

     callback(req.responseText);

   };

   request.send(data);

How does CORS work?

The CORS process adds new HTTP headers to the request, which allows the server to serve resources, but only to requests from known and trusted domains. This means that if www.attacker.com tries to access information from api.myecommercesite.com, it would not be possible because api.myecommercesite.com would not recognize www.attacker.com! For further information on CORS, you could head over the Mozilla Developer Network.

The only drawback of CORS is the lack of support from older browsers and if you don’t care about users with those old browsers, you should definitely go ahead and give CORS a try.