The state of JSONP (and JSONP vulnerabilities) in 2021
Tags: security, javascript
Published: 2021-02-07 01:53:10

## What is JSONP?

The [Same-Origin Policy](https://www.w3.org/Security/wiki/Same_Origin_Policy) is a foundational web security feature. It ensures that an attacker with control of **Site A** cannot trivially gain access to data from **Site B**. Without the Same-Origin Policy, JavaScript running on example.com could simply `fetch('https://www.facebook.com')`, read your private information, and do what it wants with it.

![](https://dev-to-uploads.s3.amazonaws.com/i/mt7ee2sx5rsm9oi3lj4i.png)
<figcaption>The Same-Origin Policy in action: screenshot of Chrome Developer Tools highlighting that an attempt to call "fetch('https://facebook.com')" while on a non-Facebook Origin is automatically blocked by the browser.</figcaption>

But what happens when the same company owns both **Site A** and **Site B** and wants to share data between them? Or when the owner of **Site B** wants to expose an API that **Site A** can access through client-side JavaScript?

These days, the answer is clear: sites can (and should) use the [CORS standard](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). With CORS, **Site B** can explicitly permit **Site A** to make certain requests.

But before CORS, there were hacks, and the most prominent one was [JSONP](https://en.wikipedia.org/wiki/JSONP).

JSONP takes advantage of the fact that the same-origin policy does not prevent execution of external `<script>` tags. Usually, a `<script src="some/js/file.js">` tag represents a static script file. But you can just as well create a dynamic API endpoint, say `/userdata.jsonp`, and have it behave as a script by:

 * Accepting a query parameter (such as `?callback=CALLBACK`)
 * Returning a `Content-Type: application/javascript` header
 * Having your server return a Javascript response that invokes the passed-in callback function name and passes it some data retrieved from the active user's session:

```js
CALLBACK({
  payload: {
    username: 'ben',
    session_id: '123'
  }
})
```
<figcaption>Example server response. The user data is retrieved serverside based on the user's session cookie and padded with a JavaScript function call.</figcaption>

Now **Site A** can add a few lines:

```html
<script>
   window.CALLBACK = function callbackFunction(userData) {
      console.log(userData.payload.username);
   }
</script>
<script src="http://api.example.com/userdata.jsonp?callback=CALLBACK"></script>
```

...and JavaScript running on **Site A** has access to the user data returned from **Site B** (api.example.com).

## (Some of) the problems with JSONP

In the example above, **Site B** is intentionally exposing unrestricted access to the logged-in user's details. Probably a bad idea! That's why sites that implement similar APIs via JSONP will typically check the `Referer`[^sic] header to see if the referring hostname is allowed, and only return session-specific data if so.

Unfortunately, checking the `Referer` header is imperfect, because:

 * There are various cases where browsers omit `Referer` headers. Also some users may have browser extensions that remove them for privacy-protection reasons, and modern browsers expose ways for requestor sites to [intentionally strip `Referer`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) from requests.
   * To account for this, developers sometimes (incorrectly) treat the case where no referrer is present the same as the case where a valid referrer is present.
   * (The `Origin` header can be used instead, but most JSONP endpoints were created to support older browsers, many of which didn't yet send the `Origin` header.)
 * In the past there were ways to fake `Referer` headers (e.g. [through Flash](https://hackerone.com/reports/10373))

This problem has left a lot of sites vulnerable over the years. There are various names in use to describe this vulnerability, but we can call the specific attack a "JSONP-based [CSRF](https://owasp.org/www-community/attacks/csrf)", and the outcome of the attack is [Information Disclosure](https://cwe.mitre.org/data/definitions/200.html), which can sometimes enable further attacks, such as [Session Hijacking](https://owasp.org/www-community/attacks/Session_hijacking_attack).

## JSONP in the wild

![Google Trends chart showing interest in "JSONP" search term beginning to rise in 2007, hit peak in 2012, and then steadily decline somewhat linearly up to the present day.](https://dev-to-uploads.s3.amazonaws.com/i/3qphnbznknpcg8halms8.png)

In the chart above we can see that interest in "JSONP" as measured by Google searches peaked in 2012 and has declined to nearly nothing since. So we know it doesn't appear to be a popular technology to use going forwards, but how much usage is still lingering on the web?

The [HTTP Archive](https://httparchive.org/) regularly crawls top sites on the web and stores various technical details. Crawl results can be queried via [Google BigQuery](https://github.com/HTTPArchive/httparchive.org/blob/master/docs/gettingstarted_bigquery.md).

Earlier, we saw that JSONP endpoints typically accept a `?callback=` GET parameter and return a `Content-Type: application/javascript` header. This gives us a heuristic to use to search through an HTTP Archive crawl and identify sites that still use JSONP:

```sql
SELECT
  REGEXP_EXTRACT(req_host, r'([^\.]+\.[^\.]+)$') as req_domain,
  MAX(url) as url,
FROM 
  `httparchive.summary_requests.2021_01_01_desktop`requests
WHERE
  type = 'script'
AND
  REGEXP_CONTAINS(url, 'callback=')
GROUP BY req_domain
```

This query lists domains that appear to expose and actively use JSONP endpoints, as well as one example JSONP endpoint URL for each. This particular crawl found 12,409 unique domains with apparent JSONP endpoints (which is 0.65% of the total number of unique domains in the crawl):

![](https://dev-to-uploads.s3.amazonaws.com/i/ke0niczuwb3rod1pneji.png)
<figcaption>Screenshot showing results of running the earlier-noted query in Google BigQuery. This is for illustrative purposes; pictured results do not contain obvious vulnerabilities, just show JSONP in active use.</figcaption>

This goes to show that even though JSONP is an outdated technique, it still has fairly significant use in the wild.

## Querying for vulnerabilities

The vast majority of the endpoints we found above are unlikely to contain vulnerable use of JSONP. Many are cases where JSONP is used to deliver relatively low-risk features like third-party widgets (e.g. a feed of recent Instagram posts) or analytics requests which don't modify or return user data.

But it's possible to refine the query down further. Through another version of the query, I found a suspicious JSONP endpoint on a major site. Then I verified that it was exploitable in the case where no `Referer` header is sent[^noref], and that it can leak user session data (I reported the issue and am leaving out identifying information here).

In the case where I did find this vulnerability, only a single modern browser (Firefox) was vulnerable. Read on for why...

## Recent web platform improvement: `SameSite` cookies

The JSONP endpoint in our example relies on session cookies to authenticate the user. Even though **Site A** can't read cookies from **Site B**, it can still request certain resources (such as the JSONP endpoint) from it. And until recently, browsers would generally send cookies along with these third-party requests. This allows the JSONP endpoint on **Site B** to return the same authenticated state that it would return to a user who visited **Site B** directly, without which the endpoint simply wouldn't function.

There were a number of issues with this default behavior, and JSONP CSRF is only one of the vulnerabilities it enabled (even leaving aside privacy issues). So a `SameSite: (Lax|Strict|None)` cookie attribute was introduced which controls whether specified cookies are sent in cross-site requests. And starting in 2020, [browsers began setting this attribute to a safe default](https://www.chromestatus.com/feature/5088147346030592). This is likely to eliminate many active vulnerabilities, because site authors now must explicitly opt in to dangerous behavior by marking cookies as `SameSite: None`. Many of the JSONP endpoints in the wild may be forgotten by their authors and will break silently, fixing the vulnerabilities (this is likely what happened in the case of the Firefox-only issue I found via the HTTP Archive); other breakages might be noticed and serve to encourage a switch to safer techniques.

MDN's [browser compatibility table here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility) shows how most modern browsers have moved to this new secure-by-default behavior:
![Extract of Mozilla Developer Network's table of browser compatibility support for SameSite=Lax and the behavior of defaulting to SameSite=Lax. All modern desktop browsers besides Firefox now are marked as defaulting to SameSite=Lax](https://dev-to-uploads.s3.amazonaws.com/i/d9226iorcb4kjptrfzqj.png)
<figcaption>Extract of SameSite=Lax browser support table from developer.mozilla.org as of 2021/01/23</figcaption>

Safari is marked as missing this improvement in the table above, but it fixed the underlying issue [by other means](https://bugs.webkit.org/show_bug.cgi?id=198181#c44) (simply blocking all third-party cookies), in mid-2020.

## Lessons

**For developers:** One big lesson is to avoid using JSONP. It's very likely that you no longer need it given that browsers as far back as IE10 had CORS support, and even large enterprises and my in-laws have long given up on IE9 (a browser released 10 years ago) at this point. (I'm not saying all sites already using JSONP should rewrite, most have low-risk use cases that involve delivering a user-agnostic response that can't result in unexpected information disclosure.)

Another lesson is to just generally be cautious about using techniques that work around web standards and the browser's default security model, but sometimes this is easier said than done. JSONP served a very useful purpose, and its ubiquity helped push the web platform to improve, encouraging browsers to build in safer options.

**For security researchers:** HTTP Archive data could be worth playing around with in BigQuery. There are a lot of possible searches that I left unexplored in this post, including a search for sites that have JSONP endpoints *and* deliberately mark some cookies as `SameSite=None` (meaning that any exploit found would more likely be exploitable cross-browser).

**For the Firefox team:** Following other browsers down the path of defaulting to `SameSite=Lax`(or following Safari in blocking all third-party cookies) sooner rather than later  would help to patch up some active vulnerabilities on the web.

[^sic]: Yes I'm spelling it correctly - the "referrer" header name is misspelled as `referer` per the [specification](https://tools.ietf.org/html/rfc7231#section-5.5.2). Thank you to whoever wrote it out that way originally, as this decision has probably saved a lot of electricity over the years.
[^noref]: In the past, you would need to be slightly more clever to ensure a `Referer` isn't sent, but thanks to a privacy feature that modern browsers have adopted, it's as simple as adding a [`referrerPolicy="no-referrer"`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/referrerPolicy) attribute to the JSONP script tag. This is an interesting illustration of unintended consequences from security and privacy improvements -- there was a time when browsers more reliably sent `Referer` headers, and developers thought they could generally assume their presence; various privacy-oriented improvements have ensured that this is no longer the case.