Common Web Application Vulnerabilities - Part 10

By Chris Patten ·

In this series of posts, my colleagues and I will dig into some specific, common web application vulnerabilities we observe regularly while performing network and application pentests. The intention of this series is to further expand upon a lot of the great information that already exists on the topic while preemptively addressing common questions we receive from our customers.

Part 10: Abusing the Same-Origin Policy: CORS and JSONP

In this post, we will cover a couple of concepts and implementations that have historically been used to circumvent the Same-Origin Policy (SOP). Specifically, we will explain the Same-Origin Policy, the JSON with Padding (JSONP) implementation and the Cross-Origin Resource Sharing (CORS) implementation. Finally, we will venture into a couple examples illustrating the use of JSONP and CORS to perform cross-origin requests.

What is the Same-Origin Policy all about?

Put simply, the same-origin policy is intended to restrict communication between web application content and the domain in which it was served from; specifically referred to as the same origin. This means that JavaScript can invoke actions and manipulate the document object model within the same origin. As a result, there has to be certain criterion that dictates appropriate access. This is achieved by examining the scheme (e.g., http or https), the host (e.g., www.example.com) and the port (e.g., 80, 443,8443, etc.).

This is better exemplified through the following table. Note that we are making a request to http://www.packetresearch.com:80.

DOMAIN REQUEST COMPARISON

Scheme://Host:PORT/

Allowed/DisALLOWED

http://www.packetresearch.com:80/

Allowed

http://www.packetresearch.com/

Allowed

http://www.packetresearch.com:80/somepage.html

Allowed

http://www.packetresearch.com/somepage.html

Allowed

http://www.packetresearch.com:9000/

Disallowed (port mismatch)

https://www.packetresearch.com:443/

Disallowed (scheme and port mismatch)

https://www.packetresearch.com/

Disallowed (scheme mismatch)

http://www.somerandomdomain.com/

Disallowed (host mismatch)

Now let’s dig into a couple of examples. First, the same-origin request as it naturally occurs between a browser and the application content served within the same domain. And then, a client side script attempting to make a cross-domain request for a resource.

The examples will be based on a two NodeJS Express servers. Specifically, one of the servers will be serving content from http://localhost:3000 and the other serving content from http://localhost:3001. Note that both of these locations are considered to be in different origins; although the scheme and host are the same, the ports are unique.

Same-Origin Request

The following code is associated with the http://localhost:3000 server. The relevant lines of code are 5 through 7, which serve a static resource called crossdomain.html. Lines 9-12 and 14-18 are essentially echo servers accessing a resource via a named route.


Figure 1: NodeJS Express server for http://localhost:3000

Next, let’s take a look at the server side code for the http://localhost:3001 server. This code is very similar as it provides the same echo server functionality as our previous server. However, there is one major difference. This server doesn’t serve static content similar to the crossdomain.html file in the previous server because the crossdomain.html resource will be used to attempt to make resource requests from the http://localhost:3000 domain to the http://localhost:3001 domain.


Figure 2: NodeJs Express server for http://localhost:3001

And finally, we look at the client-side browser code that is used to place the resource requests. This static file is served from http://localhost:3000 upon initial connection. Therefore, direct requests for resources between the browser and issuing domain are considered to be part of the same-origin policy, as we will see momentarily. The import lines in this code fall between 10 and 25, which are used to place either a GET or POST on http://localhost:3001.


Figure 3: Client side browser code

Now that the pieces are in place, let’s make a direct request to a resource served within the same-origin policy. The following request is made for http://localhost:3000/getfoo and returns “Received GET data” as expected with a successful response. Note that we are directly accessing this resource and ‘/getfoo’; therefore, the cross-domain request has not been attempted at this point.


Figure 4: A successful request for the ‘/getfoo’ resource location

That example should have been straightforward, but what about when we call load the ‘/’ location. We will do that now and discuss the differences between the ‘/getfoo’ request and the ‘/’ request.


Figure 5: The cross-domain request placed from http://localhost:3000 to http://localhost:3001

This request differs in that the crossdomain.html served by http://localhost:3000 attempts to make a request to http://localhost:3001/. However, because the DOM and JavaScript are trying to place a cross-domain request, it is disallowed access by rule of the same-origin policy.

As you can see, the same-origin policy is somewhat of a mechanism to protect application consumers from being exposed to malicious cross-domain content. However, when injection vulnerabilities are introduced into a web application, the protection can be subverted in order to circumvent restrictions imposed by the same-origin policy. As a side note, I am not even going to argue the case of a compromised web application or inherent underlying server, as that would usher in an entirely different level of problems.

Up to this point, we have explained what the same-origin policy is but not how to make cross-domain requests, so let’s segue into that right now.

Making Cross Origin Requests with JSONP

The term JSONP stands for JSON with Padding and is used to describe a method that can be leveraged to make the cross-domain request. For example, if we use an AJAX XHR to make a JSON request, then we would expect to receive a JSON document object. However, with JSONP we would make the request with a callback that would in turn provide the JSON results within a function and return as part of the response.  

The use of JQuery’s AJAX JavaScript libraries takes care of the necessary <script> tags required to facilitate our JSONP request and callback function in much the same way providing a cross-domain script would. The following example is somewhat contrived through the manipulation of the original code used for explaining the same-origin policy. Again, we will start with our modified server code and explain the relevant details.

The first piece of server-side code doesn’t change much, notwithstanding the lines between 5 and 7 in which the new static crossdomain-jsonp.html resource will be served by http://localhost:3000.


Figure 6: The server located at http://localhost:3000 that will serve the static crossdomain-jsonp.html resource

This next piece of server-side code provides the ability to provide the JSON response from http://localhost:3001 as a callback to the initial request performed from http://localhost:3000 as we will soon see. The relevant lines of code are between 8 and 18 with an emphasis on line 17, which provides the actual callback association. Note that the response will return an object with the content “Returned JSON data” as a simple example to show that is was in fact served successfully.


Figure 7: The server located at http://localhost:3001 that will serve the cross domain JSONP Response

Finally, the following client-side browser code provides the most notable change, as it leverages AJAX and the JSONP request method in order to place the cross-domain request. The lines between 11 and 21 are the most relevant, but line 12 illustrates our modified request using a callback within the constructed URL.


Figure 8: The client-side browser code leveraging the JSONP request method with callback

Now we can see this in action, so let’s fire up the servers and make a request to http://localhost:3000. A request for the ‘/’ will perform the following:

  • Request the crossdomain-jsonp.html resource.
  • Crossdomain-jsonp.html will place a JSONP request to http://localhost:3001 using AJAX.
  • The http://localhost:3001 will provide the response for JSONP content in the form of a callback.

The http://localhost:3000 will process the callback and inherent data.


Figure 9: The cross-domain request is successful and the JSON content is rendered

This is JavaScript, so JSONP essentially allows arbitrary JavaScript to influence the functionality of the requesting domain. Therefore, inherent trust is placed into the third party domain that serves the JSONP data, which can be a very insecure proposition. Next, we should talk about Cross-Origin Resource Sharing (CORS).

Making Cross-Origin Requests with CORS

The successor to JSNP is Cross-Origin Resource Sharing (CORS), as it provides a more secure method to request cross-domain content. The significant difference with CORS is the wide adoption rate by most major browser vendors. I found the following chart over at http://enable-cors.org exemplifying this fact.


Figure 10: Browser vendor’s CORS adoption rate

CORS leverages a series of recently implemented headers intended to allow cross-domain access control and authorization. The most notable headers are:

  • Origin (Header designating the source of the request)
  • Access-Control-Allow-Headers (Header designating the allowable headers)
  • Access-Control-Allow-Methods (Header designating the allowable HTTP methods)
  • Access-Control-Allow-Origin (Header designating an authorized cross-domain)

Furthermore, CORS implements the concept of “pre-flighting” the cross-domain request prior to allowing for the actual cross-domain transactions. Essentially, the requesting domain (e.g., http://localhost:3000) sets the “Origin” header with the relevant domain data. Our example would look something like the following:


Figure 11: A CORS Origin header

The responding (cross-domain) server would then return the “Access-Control” series of headers containing relevant information, such as whether or not “Access-Control-Allow-Origin” contains an authorized value. Only upon successful negotiation of header information are the servers allowed to perform the cross-domain transaction. In our example, a valid authorized response would appear as the following:


Figure 12: The CORS Access-Control headers

Next, let’s take a look at the server-side code located at http://localhost:3000 used to serve the crossdomain-cors.html static resource. Not much has changed except the line 6, which simply serves a new resource file.


Figure 13: The server located at http://localhost:3000 that will serve the static crossdomain-cors.html resource

The most significant change is how the server located at http://localhost:3001 will handle the CORS request. On lines 5-12 of the code, we see that headers are set and returned upon a cross-domain request. Specifically, the “Origin” header contains the value of “http://localhost:3000/”, which coincides with the authorized domain value set in the “Access-Control-Allow-Origin” header.

One of the most significant misconfigurations that can put cross-domain requestors at risk is an overly permissive CORS cross-domain policy. For instance, the value ‘Access-Control-Allow-Origin=”*”’ permits cross-domain requests from anywhere (i.e., no restriction at all).


Figure 14: The server located at http://localhost:3001 that will authorize and serve the cross-domain transaction

The client-side browser code does not need to change at all and simply leverages JQuery AJAX requests to facilitate the CORS request. Note that the browser will add the “Origin” header and will pre-flight the negotiation as expected with a bit of overhead associated with the additional preliminary http request.


Figure 15: The client-side browser code used to facilitate the CORS request

Now we can see this in action, so let’s fire up the servers and make a request to http://localhost:3000. A request for the ‘/’ will perform the following:

  • Request the crossdomain-cors.html resource.
  • Crossdomain-cors.html will place a CORS request to http://localhost:3001 using AJAX\.
  • The Origin header will be sent with the respective requesting domain.
  • The http://localhost:3001 will provide “Access-Control” headers with authorization.
  • The http://localhost:3000 will process the returned data.


Figure 16: The cross-domain CORS request is successful and our content is rendered

Conclusion

As with any technology used to share information, the means to interact with numerous resources has to be closely evaluated for necessity and appropriateness. Further, secure access controls should be implemented, which provide adequate protection intended to safeguard application consumers and their inherent data. The exposure to understanding the fundamental mechanics of these two prevalent cross-domain resource sharing methods (i.e., JSONP and CORS) should hopefully allow developers to make educated decisions when designing secure applications.

Additional Posts