Practical JSONP Injection

JSONP injection is a lesser known but quite widespread and dangerous vulnerability and it surfaced in the last years due to the high rate of adoption of JSON, web APIs and the urging need for cross-domain communications.

What is JSONP?

Assuming everybody knows what JSON is, let’s talk a little about JSONP. JSONP comes from JSON with Padding and it was created in order to bypass common restrictions such as Same-origin Policy which is enforced for XMLHttpRequest (AJAX requests).

Let’s take an example. Our online banking application, http://verysecurebank.ro, has implemented an API call that returns the current user’s transactions.

An HTTP request to the http://verysecurebank.ro/getAccountTransactions endpoint presents us with the transactions, JSON formatted:

json-transactions

If our reports application, accessible at http://reports.verysecurebank.ro wants to get the transaction details, an AJAX call to the page won’t be possible, due to Same-origin Policy being in effect (different host).

sop-json

To get around this problem, JSONP came into play. Since Cross-domain script inclusion (mostly used to externally load JavaScript libraries such as jQuery, AngularJS etc.) is allowed, but not recommended, a smart trick apparently solved the entire equation: prepending the response with a callback.

Note: even if it might be obvious, it’s worth mentioning that when including a script cross-domain, it will run in the context of the including application, not in the source’s context.

Adding a callback to the API response, wrapped around the JSON formatted data, allows us to load the API response between script tags and get its content by defining our own callback function to handle it.

Exploitation

These are the most common scenarios you will come across:

  1. the callback function is hardcoded within the response
    1. basic function call
    2. object method call
  2. the callback function is dynamic
    1. completely controllable from the URL (GET variable)
    2. partially controllable from the URL (GET variable), but appended with a number
    3. controllable from the URL (GET variable), but initially not displayed in the request

Basic function call

A very common example where the myCallback callback is hardcoded within the response, wrapped around the JSON formatted data:

callback-example

We can easily exploit this by defining the myCallback function first and afterwards referencing the API call within script tags:

callback-example-code

Note: Make sure you define your function before including the response, otherwise an undefined function will be called and you won’t get any data.
When the logged in victim visits our malicious page, we grab his data. For the sake of brevity, we display the data in the current page, neatly formatted.

callback-example-result

Object method call

This is almost identical with the first example and you might encounter it in ASP or ASP.NET web applications. In our example, System.TransactionData.Fetch is added as a callback around the JSON formatted data:

multiple-callback-example

We simply create the Fetch method for the TransactionData object which is already part of the System object.

multiple-callback-code

The result is the same, so no screenshots from now on.

Completely controllable from the URL (GET variable)

This is the most common scenario you will come across. The callback function is specified in the URL and we can completely control it. The callback URL parameter allows us to change the name of the callback, so we set it to testing and see it changed within the response:

specified-callback-example

We can basically use the same exploit code, just don’t forget to add the callback parameter when including the response with script tags.

specified-callback-code

Partially controllable from the URL (GET variable), but appended with a number

In this scenario, the callback function name is appended with something, usually a number. In most cases, we get something like jQuery and a short number appended to it, like 12345, the callback becoming jQuery12345.

appended-callback-example

Logically, the exploit code remains the same, we just have to add 12345 only to our callback function name, and not when including the script.

appended-callback-code

But what if the number is not hardcoded? What if it’s dynamic and different for each session? If it’s a relatively short number we can programmatically pre-define functions for each possibility. Let’s suppose the appended number goes up to 99.999. We can programmatically create all these functions so whatever number is appended, we already have a callback function for it. Here is the sample code, I used a simpler callback just to illustrate the result:

appended-callback-code-generate

What happens here: we have the hardcoded callback name jQuery, we set a limit for the number of functions. In the first loop we generate the callback function names in the callbackNames array. Then we loop through the array and turn each callback name into a global function. Please note that in order to shorten up the code I only alert the amount of money sent in the first transaction. Let’s see how it works:

appended-callback-alert

On my machine, it took around 5 seconds to display the alert with the callback name being jQuery12345. This means it took Chrome under 5 seconds to create over 10.000 functions, so I would boldly say it’s a pretty doable exploitation.

Controllable from the URL (GET variable), but initially not displayed in the request

The last scenario involves an API call that apparently does not have a callback, so no visible JSONP. This might happen when the developers leave “hidden” backwards compatibility with other software or the code simply was not removed at refactoring. So, when seeing an API call without callback, especially if the JSON formatted data is already between parentheses, manually add the callback to the request.

If we have the following API call http://verysecurebank.ro/getAccountTransactions, we might try to guess the callback variable:

While these are the most common callback names, feel free to guess some more. If our callback is added to the response, bingo, let’s ethically grab some data.

Basic data grabbing

Since we only displayed the data until now, let’s see how we can send it back to us. This is a minimal example of JSONP data grabbing you can use as a proof of concept.

steal-code

We make a GET request to our data grabber with the application response (transactions) in the data parameter.

Note: make sure you use JSON.stringify() on the data since it is an object, and we don’t want to end up with only [object Object] in our file.

Note: if the response is large, make sure you switch to POST since you may not receive the complete data due to HTTP GET size restrictions.

Here is our grabData.php code, we append the received data in the data.txt file:

steal-php

Common issues

While hunting web applications for JSONP vulnerabilities, we might encounter some issues. Here we try to tackle them.

Content-Type and X-Content-Type-Options

If in the response headers of the API request, the X-Content-Type-Options is set to nosniff, the Content-Type must be set to JavaScript (text/javascript, application/javascript, text/ecmascript etc.) to work on all browsers. This happens because by including the callback within the response, the response is no longer JSON, but JavaScript.

Point your browser to https://mathiasbynens.be/demo/javascript-mime-type if you want to know which Content-Types your browser interprets as JavaScript.

In this example, Content-Type is set to application/json and X-Content-Type-Options to nosniff.

content-type-error

Latest versions of Google Chrome, Microsoft Edge and Internet Explorer 11 successfully blocked the script execution. However, Firefox 50.1.0 (currently the latest version) didn’t.

Note: If the X-Content-Type-Options: nosniff header is not set, it will work on all aforementioned browsers.

Note: Older versions of browsers do not take into consideration strict MIME type checking, since X-Content-Type-Options was recently implemented. According to Mozilla, this is the browser compatibility:

browser-compatibility

Response Codes

Sometimes we might get some other response codes than 200, especially since we are messing with the response. I’ve run a few tests on these browsers:

  • Microsoft Edge 38.14393.0.0
  • Internet Explorer 11.0.38
  • Google Chrome 55.0.2883.87
  • Mozilla Firefox 50.1.0

These inconsistencies were found:

Response Code Works in
100 Continue Internet Explorer, Microsoft Edge, Google Chrome
101 Switching Protocols Google Chrome
301 Moved Permanently Google Chrome
302 Found Google Chrome
304 Not Modified Microsoft Edge


So even if we don’t get a 200 HTTP code, the vulnerability is still exploitable in other browsers.

Referrer check bypass

  1. using data URI scheme

If there is a HTTP Referer check, we can try not to send it in order to bypass the verification. How can we do that? Introducing Data URI.
We can abuse data URI scheme in order to make the request without a HTTP Referer. Since we are dealing with code, which includes quotes, double quotes and other syntax breaking characters, we are going to base64 encode our payload (callback definition and script inclusion).

This is the syntax: data:text/plain;base64,our_base64_encoded_code

iframe-src

Here are the three main HTML tags that allow us to use the data URI scheme:

    • iframe (in the src attribute) – it does not work in Internet Explorer
    • embed (in the src attribute) – it does not work in Internet Explorer and Microsoft Edge
    • object (in the data attribute) – it does not work in Internet Explorer and Microsoft edge

We can see that no HTTP Referer was sent in the API request.

referer-browser

2. request from HTTPS to HTTP

We can also avoid sending a HTTP Referer by hosting our code on a HTTPS page, if our target website can be accessed via HTTP. If we issue a HTTP request from a HTTPS page, the browser is instructed not to send the Referer header in order to prevent information leakage.
All we have to do is to host our malicious code on a HTTPS enabled website.

Note: due to mixed-content security mechanism, this does not work on modern web browsers with the default settings. The victim has manually accept the security warnings of the browsers.

mixed-content

However, it works in older versions of the browsers, and the HTTP Referer header is not sent, as we can see:

https-to-http

How we can fix this

Lastly, let’s see how we can prevent this from happening. The most straightforward and modern approach is CORS (Cross-Origin Resource Sharing).

  1. completely remove JSONP functionality
  2. add the Access-Control-Allow-Origin header to your API response
  3. use cross-domain AJAX requests

So, http://reports.verysecurebank.ro embeds the following cross-domain AJAX request to http://verysecurebank.ro/getAccountTransactions:

ajax-request-code

The API response includes the Access-Control-Allow-Origin: http://reports.verysecurebank.ro:

ajax-request-headers

And we get the content of http://verysecurebank.ro/getAccountTransactions:

ajax-request-content

Conclusions

Although JSONP usage is decreasing, there is still a large number of websites that still use or support it. As a final tip, when dealing with JSONP also don’t forget to check for Reflected File Download and Reflected Cross-Site Scripting vulnerabilities. Happy pwning!

17 comments

  1. Hi Petre- Thanks for explaining it in so depth.

    In conclusion you told to look for xss too, but afaik xss isn’t possible in modern browser except IE if the response is in json. Could you let me know how it is possible or what do you mean?

    Like

    1. Hello,

      You are right. If the Content-Type header is set to JSON or JavaScript, you can only exploit it on older browser versions. But if the Content-Type header is not present, you can get an XSS on modern browsers too.

      Like

  2. Hello Petre , Thanks for Writing this into Detail.

    is it Possible for you to Share Vulnerable JSONP Code to Understand it in more Depth as Well as Practice it in Lab .

    If Yes Then Please Upload it to Github

    Thanks Again !

    Like

    1. Hello,

      Sorry, but there is no real application developed. I only created a few pages that return the presented content.

      Like

  3. Great post. I was checking constantly this blog and I am impressed!
    Extremely useful information specially the remaining part 🙂 I take care of such information much.
    I used to be looking for this certain information for a long time.
    Thanks and good luck.

    Like

  4. Hi!

    I’m trying to make an AJAX call and based on my research in the internet, to overcome cross-domain issue, jsonp is recommended.

    Digging into jsonp (I’m not that familiar with ajax), I noticed that instead of making an ajax call, it mentions doing script tag Injection.

    My questions:
    1 – Is it better to do script tag injection when referring to jsonp, or an ajax call is alright? Which approach would be more efficient?

    2 – Also, I wish I could have error handling working, but can’t since jsonp does not do error handling. Is there another work around except for the timeout function in ajax call?

    I’m currently using script injection and expecting a response return. At times, I don’t see the response. I’m trying to find a way to catch if the script injection failed, thus returning no response.

    Thanks for answering my questions!

    Like

Leave a Reply