CORS Findings: Another Way to Comprehend

CORS Findings: Another Way to Comprehend

by Ryan Leese

When I first started learning about Cross Origin Resource Sharing (CORS) as it applies to web application pentesting, I found it was difficult to gather information needed to fully grasp the security implications of common CORS misconfigurations. (Spoiler: If Burp Suite lights up red like below, things can get pretty ugly!)

I think there’s a more practical way to explain CORS findings that others may find helpful. I find that many explanations tend to demonstrate what happens with CORS, but fail to demonstrate why it’s happening or where the security decisions are being made under the hood. I intend to provide the CORS explanation I wish I could have found in my own research. If you’ve ever found yourself less-than-prepared to answer prying questions about Burp Suite CORS findings, or you simply lack an instinctual understanding of Same-Origin Policy, this blog post is for you.

Figure 1 – Dangit, Burp Suite! Now I Gotta Figure Out How to Explain This!

Understanding Same-Origin Policy

First off, it helps to lay the groundwork for how things work without CORS in the picture. CORS is a relatively new development in web security, and there are still many servers that don’t make use of it. By default, all modern web browsers follow a strict set of rules that, among other things, dictate whether JavaScript code is allowed to read information in HTTP/s responses. These rules are known as Same-Origin Policy.

Put simply for our purposes, browsers adhering to Same-Origin Policy prevent code that is running on one web site (aka an origin) from accessing HTTP responses from another web site which may contain sensitive information. Same-Origin rules are more nuanced than this, but this is the important information for understanding how to attack a poor CORS configuration. To truly understand what is happening under the hood with both Same-Origin and CORS, it’s important to remember that the web browser is what’s making the decisions about what scripting activity is allowed.

2. MaliciousWebSite.com provides a malicious script that instructs the Victim Web Browser to make a request that includes cookies to MyBankAccount.com. The Origin of this malicious code is “https://MaliciousWebSite.com”.

3. The Victim Browser dutifully submits the request that includes any cookies it has stored for MyBankAccount.com. This could include session cookies.

4. MyBankAccount.com responds with private bank account information such as account balances, PII, etc. The Origin of this response is “https://MyBankAccount.com”.

5. The malicious script running in the Victim Web Browser attempts to access the response coming back from MyBankAccount.com. The Victim Web Browser compares the origin of the malicious script to that of the response. Because these two origins differ, the malicious script is unable to access the response containing the private data.

Understanding CORS

Once you have an intuitive understanding of how browsers use Same-Origin Policy to block Cross-Origin transfers of information, it’s relatively simple to make the logical hop understanding where CORS fits in to the picture. CORS is a mechanism to relax Same-Origin Policy rules for certain origins. CORS allows a web server to inform web browsers about which other origins should be allowed to access content from that web server’s replies. (Feel free to read that a couple times to let it soak in. )

In modern web apps, this feature can come in very handy when a server wants to make its content accessible to other servers or applications. Applications with a distributed infrastructure can make good use of CORS. A good example would be YouTube’s HTML5 video player requesting private audio and video data from YouTube content servers. In this case, the CORS process happens like this:

1. By including an “Origin” http request header, the YouTube HTML5 video player running in the browser indicates it wants to make a cross-origin request to access audio and video content on https://r2—sn-vgqsrnes.c.youtube.com. In this case, the video player’s code came from https://www.youtube.com, so the browser indicates that URL as the Origin. Because the requested video is private, the video player also chooses to send cookies that are in scope for the cross-origin request.

2. The content server notices this is a cross-origin request and forms a response that includes CORS response headers (see screenshot below) to relay information to the web browser about how the response should be handled:

Access-Control-Allow-Origin: https://www.youtube.com

This response header indicates to the browser that only code from the origin “https://www.youtube.com” should be permitted to read the information in this response.

Access-Control-Allow-Credentials: true

This response header indicates to the browser that it’s okay for allowed origins to read the information in this response even though cookies were used to get this response.

3. The browser receives the response from the content server https://r2—sn-vgqsrnes.c.youtube.com. Important: at this point, the HTML5 player has not been given access to the audio/video data from the reply; the browser has only received the response and is storing it in memory.

4. The browser determines if the video player is permitted to receive the response (which the browser has already received). The browser checks the Access-Control-Allow-Origin response header to see if it matches the origin of the code for the HTML5 video player. Additionally, because this is a response to a cookied request, the Access-Control-Allow-Credentials response header must be true.

Note: As mentioned before, the browser is in charge of the security decisions in Step 4. The content server is only providing Access-Control response headers to the browser so the browser can decide what should be permitted in the processing of the response.

5. After the CORS response headers pass all security checks within the browser, the response data is handed over to the HTML5 video player code to allow the video to begin playing. If these CORS checks fail, the browser defaults to enforcing Same-Origin Policy.

Where It Can All Go Wrong

Now let’s build on the Same-Origin Policy chart to include CORS logic.

Figure 5 – CORS Process Flow

1. The Victim Web Browser visits MaliciousWebSite.com.

2. MaliciousWebSite.com provides a malicious script that instructs the Victim Browser to make a request that includes cookies to MyBankAccount.com. The Origin of this malicious code is “https://MaliciousWebSite.com”.

3. The Victim Browser dutifully submits the request that includes its MyBankAccount.com session cookies.

4. MyBankAccount.com responds with private bank account information such as account balances, PII, etc. The Origin of this response is “https://MyBankAccount.com”.

5. The Victim Browser receives the response and examines the CORS headers to see if MyBankAccount.com indicates code from the origin “https://MaliciousWebSite.com” is permitted to read the response.

6. If the CORS response headers from MyBankAccount.com permit cookied, cross-origin communication to the origin https://MaliciousWebSite.com, the Victim Browser will give the malicious script access to the response contents containing private financial data. If the cross-origin communication is not allowed by the response headers, the browser will enforce Same-Origin Policy and block the Malicious Script from reading response content.

The bolded text in Step 6 indicates how an attack can easily happen with a poor CORS configuration. You might ask, “But why would MyBankAccount.com permit browsers to perform Cross-Origin communication with MaliciousWebSite.com?” Well, unfortunately it’s quite a common occurrence for CORS configurations on servers to respond with Access-Control-Allow-Origin/Credentials headers that automatically permit any arbitrary origin that is performing a cookied request. This is bad news for any site that uses cookies to store session tokens.

Hopefully by now I’ve made it clear that if a web server trusts arbitrary origins and also allows cookies/credentials to be passed, code from any web server on the Internet can potentially steal private user data when visited by the victim.

1. Identify if the target application accepts arbitrary CORS origins. There are a couple easy ways to do this:

a. Use Burp Suite’s Repeater to add an “Origin” HTTP header to a request that returns private user information. The origin can be anything for the purposes of discovering this vulnerability. If the response contains both Access-Control-Allow-Origin: [your arbitrary origin] and Access-Control-Allow-Credentials:true headers, prepare to cause a ruckus.

b. Or use Burp Suite’s Scanner to perform an active scan on a cookied request that returns sensitive user information. If you receive a red, high-severity CORS arbitrary origin finding, you’re good to go.

2. Build a malicious page that has JavaScript that attempts to make a cross-origin request to a vulnerable CORS server page that has private user data protected by cookies. I have put together a useful POC that can be found here:

https://github.com/trustedsec/cors-poc. corstest.html handles auto-submitting the cross-origin request, and cgi-bin/postlogger.py listens for the private data that is POSTed by corstest.html.

3. Spin up the web server to serve up our script. One really easy way to accomplish this is to cd to the directory containing corstest.html, then run python3 -m http.server –cgi. Be careful where you run this from; it will serve all the files in the current and sub directories over HTTP on port 8000.

4. Direct a user that is already logged in to the target application to execute the script we configured. In practice, this could be done any number of ways including phishing, cross-site scripting, malicious advertisements, etc.

5. Watch the logs on the web server. If the exploit was successful, you’ll see POST data from the victim’s browser.

FAQ

Q: What about when the target server responds with Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true?

A: A wildcard may look dangerous here, but web browsers are configured to disallow cross-origin communication with credentials in this configuration due to how dangerous it would be to allow lazy developers to use this. An explicit origin must be called out in Access-Control-Allow-Allow-Origin for browsers to permit cookied cross-origin requests.

Q: What if Access-Control-Allow-Credentials isn’t set to ‘true’?

A: Extracting private user data will not be a possibility because the browser will not process responses that were from a cookied request.

Ryan brings 4+ years of penetration testing and vulnerability management experience built on a mixed history of administering Windows/Linux server, enterprise VMware, and Cisco products. Ryan specializes in web application pentesting to provide knowledgeable application of advanced tactics to identify security issues in web applications and services. The use of test-driven python programming to develop new toolsets and automate existing processes allows Ryan to deliver tremendous value.