This site is the archived OWASP Foundation Wiki and is no longer accepting Account Requests.
To view the new OWASP Foundation website, please visit https://owasp.org

Difference between revisions of "CORS OriginHeaderScrutiny"

From OWASP
Jump to: navigation, search
m (Complete review of the article and linked code sample in order to fix a critical bug.)
(That's really all you need to know - don't trust the origin header, do your own authentication. Full stop. This additional rambling about having to manage users and passwords is muddying the waters, and isn't neces. true (not all auth is password auth))
 
(6 intermediate revisions by 4 users not shown)
Line 1: Line 1:
Last revision (mm/dd/yy): '''08/16/2013'''
+
{{taggedDocument
 +
| type=pls_review
 +
}}
  
 
== Introduction ==
 
== Introduction ==
Line 5: Line 7:
 
'''CORS''' stands for '''C'''ross-'''O'''rigin '''R'''esource '''S'''haring.  
 
'''CORS''' stands for '''C'''ross-'''O'''rigin '''R'''esource '''S'''haring.  
  
Is a feature offering the possbility for:
+
Is a feature offering the possibility for:
 
* A web application to expose resources to all or restricted domain,
 
* A web application to expose resources to all or restricted domain,
 
* A web client to make AJAX request for resource on other domain than is source domain.
 
* A web client to make AJAX request for resource on other domain than is source domain.
  
This article will focus on role of the '''Origin''' header in exchange between web client and web application.
+
This article will focus on the role of the '''Origin''' header in the exchange between web client and web application.
  
The basic process is composed by steps below (sample HTTP resquest/response has been taken from [https://developer.mozilla.org/en-US/docs/HTTP_access_control Mozilla Wiki]):
+
The basic process is composed of the steps below (sample HTTP request/response has been taken from [https://developer.mozilla.org/en-US/docs/HTTP_access_control Mozilla Wiki]):
  
* '''Step 1 : Web client send request to get resource from a different domain.'''
+
* '''Step 1 : Web client sends a request to get a resource from a different domain.'''
  
 
<pre>
 
<pre>
Line 30: Line 32:
 
</pre>
 
</pre>
  
The web client inform is source domain using the HTTP request header "'''Origin'''".
+
The web client tells the server its source domain using the HTTP request header "'''Origin'''".
  
* '''Step 2 : Web application respond to request.'''
+
* '''Step 2 : Web application response to request.'''
  
 
<pre>
 
<pre>
Line 47: Line 49:
 
</pre>
 
</pre>
  
The web application informs web client of the allowed domain using the HTTP response header '''Access-Control-Allow-Origin'''.
+
The web application informs the web client of the allowed domains using the HTTP response header '''Access-Control-Allow-Origin'''.
The header can contains a '*' to indicate that all domain are allowed OR a specified domain to indicate the specified allowed domain.
+
The header can contains either a '*' to indicate that all domains are allowed OR a specified domain to indicate the specified allowed domain.
  
 
* '''Step 3 : Web client process web application response.'''
 
* '''Step 3 : Web client process web application response.'''
Line 57: Line 59:
 
== Risk ==
 
== Risk ==
  
''A reminder : Into this article we focus on web application side because it's the only part in which we have the maximum of control.''
+
''A reminder : This article will focus on the web application side because it's the only part in which we have the maximum of control.''
  
 
The risk here is that a web client can put any value into the '''Origin''' request HTTP header in order to force web application to provide it the target resource content.  
 
The risk here is that a web client can put any value into the '''Origin''' request HTTP header in order to force web application to provide it the target resource content.  
In the case of a Browser web client, the header value is managed by the browser but another "web client" can be used (like Curl/Wget/Burp suite/...) to change/override the "Origin" header value...
+
In the case of a Browser web client, the header value is managed by the browser but another "web client" can be used (like Curl/Wget/Burp suite/...) to change/override the "Origin" header value. For this reason it is not recommended to use the Origin header to authenticate requests as coming from your site.
  
 
== Countermeasure ==
 
== Countermeasure ==
  
'''Option A: Use CORS authenticated request'''
+
Enable authentication on the resources accessed and require that the user/application credentials be passed with the [https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Requests_with_credentials CORS requests].
 
 
In this option, we enable authentication on the resources accessed and require that the user/application credentials be passed with the [https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Requests_with_credentials CORS requests].
 
 
 
If the CORS resources exposed are classified as sensitive (and CORS exposition is mandatory) it's a good option but if the objective is only to ensure that the request originator is really
 
one of the allowed (to avoid rogue call), there somes drawback with this option, among others:
 
* The target application must manage users (or applications) credentials repositories including features like password expiry, password reset, brute force prevention, account lock/unlock,...
 
* The client application must store (in a secure way) the credentials to use,
 
* The client application must manage/configure credentials transfer in HTTP request in order that credentials are send only in case of CORS requests to target application.
 
 
 
'''Option B: We can scrutiny the Origin header value on server side'''
 
 
 
In this option, the objective is to work between the step 1 and 2 of the CORS HTTP requests/responses exchange process (see above).
 
 
 
To achieve it, we will use JEE Web Filter that will ensure the following points for earch incoming HTTP CORS requests:
 
# Have only one and non empty instance of the '''origin''' header,
 
# Have only one and non empty instance of the '''host''' header,
 
# The value of the origin header is present in a internal allowed domains list (white list). As we act before the step 2 of the CORS HTTP requests/responses exchange process, allowed domains list is yet provided to client,
 
# Cache IP of the sender for 1 hour. If the sender send one time a origin domain that is not in the white list then all is requests will return an HTTP 403 response (protract allowed domain guessing).
 
 
 
We use the method above because it's not possible to identify up to 100% that the request come from one expected client application, since:
 
* All information of a HTTP request can be faked,
 
* It's the browser (or others tools) that send the HTTP request then the IP address that we have access to is the client IP address.
 
 
 
<pre style="color:#88421D">In a Enterprise inter application communication it's possible to add a check on the client IP range.
 
"Business to business" communication offer possbility to for each part to indicate to others parts the IP range that it will be used by its applications.</pre>
 
 
 
'''Sample implementation: Filter class'''
 
 
 
<pre>
 
 
 
import java.io.IOException;
 
import java.util.ArrayList;
 
import java.util.Collections;
 
import java.util.Enumeration;
 
import java.util.List;
 
 
 
import javax.servlet.Filter;
 
import javax.servlet.FilterChain;
 
import javax.servlet.FilterConfig;
 
import javax.servlet.ServletException;
 
import javax.servlet.ServletRequest;
 
import javax.servlet.ServletResponse;
 
import javax.servlet.annotation.WebFilter;
 
import javax.servlet.http.HttpServletRequest;
 
import javax.servlet.http.HttpServletResponse;
 
 
 
import net.sf.ehcache.Cache;
 
import net.sf.ehcache.CacheManager;
 
import net.sf.ehcache.Element;
 
import net.sf.ehcache.config.CacheConfiguration;
 
import net.sf.ehcache.config.PersistenceConfiguration;
 
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
 
 
 
/**
 
* Sample filter implementation to scrutiny CORS "Origin" HTTP header.<br/>
 
*
 
* This implementation has a dependency on EHCache API because<br/>
 
* it use Caching for blacklisted client IP in order to enhance performance.
 
*
 
* Assume here that all CORS resources are grouped in context path "/cors/".
 
*
 
*/
 
@WebFilter("/cors/*")
 
public class CORSOriginHeaderScrutiny implements Filter {
 
 
 
/** Filter configuration */
 
@SuppressWarnings("unused")
 
private FilterConfig filterConfig = null;
 
 
 
/** Cache used to cache blacklisted Clients (request sender) IP address */
 
private Cache blackListedClientIPCache = null;
 
 
 
/** Domains allowed to access to resources (white list) */
 
private List<String> allowedDomains = new ArrayList<String>();
 
 
 
/**
 
* {@inheritDoc}
 
*
 
* @see Filter#init(FilterConfig)
 
*/
 
@Override
 
public void init(FilterConfig fConfig) throws ServletException {
 
// Get filter configuration
 
this.filterConfig = fConfig;
 
// Initialize Client IP address dedicated cache with a cache of 60 minutes expiration delay for each item
 
PersistenceConfiguration cachePersistence = new PersistenceConfiguration();
 
cachePersistence.strategy(PersistenceConfiguration.Strategy.NONE);
 
CacheConfiguration cacheConfig = new CacheConfiguration().memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.FIFO)
 
.eternal(false)
 
.timeToLiveSeconds(3600)
 
.diskExpiryThreadIntervalSeconds(450)
 
.persistence(cachePersistence)
 
.maxEntriesLocalHeap(10000)
 
.logging(false);
 
cacheConfig.setName("BlackListedClientsCacheConfig");
 
this.blackListedClientIPCache = new Cache(cacheConfig);
 
this.blackListedClientIPCache.setName("BlackListedClientsCache");
 
CacheManager.getInstance().addCache(this.blackListedClientIPCache);
 
// Load domains allowed white list (hard coded here only for example)
 
this.allowedDomains.add("http://www.html5rocks.com");
 
this.allowedDomains.add("https://www.mydomains.com");
 
}
 
 
 
/**
 
* {@inheritDoc}
 
*
 
* @see Filter#destroy()
 
*/
 
@Override
 
public void destroy() {
 
// Remove Cache
 
CacheManager.getInstance().removeCache("BlackListedClientsCache");
 
}
 
 
 
/**
 
* {@inheritDoc}
 
*
 
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
 
*/
 
@Override
 
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
HttpServletRequest httpRequest = ((HttpServletRequest) request);
 
HttpServletResponse httpResponse = ((HttpServletResponse) response);
 
List<String> headers = null;
 
boolean isValid = false;
 
String origin = null;
 
String clientIP = httpRequest.getRemoteAddr();
 
 
 
/* Step 0 : Check presence of client IP in black list */
 
if (this.blackListedClientIPCache.isKeyInCache(clientIP)) {
 
// Return HTTP Error without any information about cause of the request reject !
 
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
 
// Add trace here
 
// ....
 
// Quick Exit
 
return;
 
}
 
 
 
/* Step 1 : Check that we have only one and non empty instance of the "Origin" header */
 
headers = CORSOriginHeaderScrutiny.enumAsList(httpRequest.getHeaders("Origin"));
 
if ((headers == null) || (headers.size() != 1)) {
 
// If we reach this point it means that we have multiple instance of the "Origin" header
 
// Add client IP address to black listed client
 
addClientToBlacklist(clientIP);
 
// Return HTTP Error without any information about cause of the request reject !
 
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
 
// Add trace here
 
// ....
 
// Quick Exit
 
return;
 
}
 
origin = headers.get(0);
 
 
 
/* Step 2 : Check that we have only one and non empty instance of the "Host" header */
 
headers = CORSOriginHeaderScrutiny.enumAsList(httpRequest.getHeaders("Host"));
 
if ((headers == null) || (headers.size() != 1)) {
 
// If we reach this point it means that we have multiple instance of the "Host" header
 
// Add client IP address to black listed client
 
addClientToBlacklist(clientIP);
 
// Return HTTP Error without any information about cause of the request reject !
 
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
 
// Add trace here
 
// ....
 
// Quick Exit
 
return;
 
}
 
 
 
/* Step 3 : Perform analysis - Origin header is required */
 
if ((origin != null) && !"".equals(origin.trim())) {
 
if (this.allowedDomains.contains(origin)) {
 
// Check if origin is in allowed domain
 
isValid = true;
 
} else {
 
// Add client IP address to black listed client
 
addClientToBlacklist(clientIP);
 
isValid = false;
 
// Add trace here
 
// ....
 
}
 
}
 
 
 
/* Step 4 : Finalize request next step */
 
if (isValid) {
 
// Analysis OK then pass the request along the filter chain
 
chain.doFilter(request, response);
 
} else {
 
// Return HTTP Error without any information about cause of the request reject !
 
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
 
}
 
}
 
 
 
/**
 
* Blacklist client
 
*
 
* @param clientIP Client IP address
 
*/
 
private void addClientToBlacklist(String clientIP) {
 
// Add client IP address to black listed client
 
Element cacheElement = new Element(clientIP, clientIP);
 
this.blackListedClientIPCache.put(cacheElement);
 
}
 
 
 
/**
 
* Convert a enumeration to a list
 
*
 
* @param tmpEnum Enumeration to convert
 
* @return list of string or null is input enumeration is null
 
*/
 
private static List<String> enumAsList(Enumeration<String> tmpEnum) {
 
if (tmpEnum != null) {
 
return Collections.list(tmpEnum);
 
}
 
return null;
 
}
 
}
 
 
 
</pre>
 
 
 
 
 
 
 
'''Note:'''
 
[[Automated Audit using W3AF|W3AF]] audit tools (http://w3af.org) contains plugins to automatically audit web
 
application to check if they implements this type of countermeasure.
 
 
 
<pre style="color:#088A08">
 
It's very useful to include this type of tools into a web application development process in order to
 
perform a regular automatic first level check (do not replace an manual audit and manual audit must be also conducted regularly).
 
</pre>
 
 
 
 
 
  
 +
It is not possible to be 100% certain that any request comes from an expected client application, since all information of a HTTP request can be faked.
 
== Informations links ==
 
== Informations links ==
  
Line 303: Line 76:
 
* CORS Abuse : http://blog.secureideas.com/2013/02/grab-cors-light.html
 
* CORS Abuse : http://blog.secureideas.com/2013/02/grab-cors-light.html
  
 
+
[[Category:Java]]
[[Category:OWASP Java Project]]
 
 
[[Category:Attack]]
 
[[Category:Attack]]
 
[[Category:Injection Attack]]
 
[[Category:Injection Attack]]

Latest revision as of 20:05, 25 February 2019

This Page has been flagged for review. Please help OWASP and review this Page to FixME.

Introduction

CORS stands for Cross-Origin Resource Sharing.

Is a feature offering the possibility for:

  • A web application to expose resources to all or restricted domain,
  • A web client to make AJAX request for resource on other domain than is source domain.

This article will focus on the role of the Origin header in the exchange between web client and web application.

The basic process is composed of the steps below (sample HTTP request/response has been taken from Mozilla Wiki):

  • Step 1 : Web client sends a request to get a resource from a different domain.
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example

[Request Body]

The web client tells the server its source domain using the HTTP request header "Origin".

  • Step 2 : Web application response to request.
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
Access-Control-Allow-Origin: *

[Response Body]

The web application informs the web client of the allowed domains using the HTTP response header Access-Control-Allow-Origin. The header can contains either a '*' to indicate that all domains are allowed OR a specified domain to indicate the specified allowed domain.

  • Step 3 : Web client process web application response.

According to the CORS W3C specification, it's up to the web client (usually a browser) to determine, using the web application response HTTP header Access-Control-Allow-Origin, if the web client is allowed to access response data.

Risk

A reminder : This article will focus on the web application side because it's the only part in which we have the maximum of control.

The risk here is that a web client can put any value into the Origin request HTTP header in order to force web application to provide it the target resource content. In the case of a Browser web client, the header value is managed by the browser but another "web client" can be used (like Curl/Wget/Burp suite/...) to change/override the "Origin" header value. For this reason it is not recommended to use the Origin header to authenticate requests as coming from your site.

Countermeasure

Enable authentication on the resources accessed and require that the user/application credentials be passed with the CORS requests.

It is not possible to be 100% certain that any request comes from an expected client application, since all information of a HTTP request can be faked.

Informations links