Difference between revisions of "CORS OriginHeaderScrutiny"

From OWASP
Jump to: navigation, search
m
m
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Last revision (mm/dd/yy): '''10/12/2012'''
+
Last revision (mm/dd/yy): '''08/16/2013'''
  
 
== Introduction ==
 
== Introduction ==
 +
 
'''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 an feature offering the possbility to:
+
 
* 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 made 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 role of the '''Origin''' header in 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 by steps below (sample HTTP resquest/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 send request to get resource from a different domain.'''
 
 
<pre>
 
<pre>
 
GET /resources/public-data/ HTTP/1.1
 
GET /resources/public-data/ HTTP/1.1
Line 32: Line 29:
 
[Request Body]
 
[Request Body]
 
</pre>
 
</pre>
 +
 
The web client inform is source domain using the HTTP request header "'''Origin'''".
 
The web client inform is source domain using the HTTP request header "'''Origin'''".
 
  
 
* '''Step 2 : Web application respond to request.'''
 
* '''Step 2 : Web application respond to request.'''
 +
 
<pre>
 
<pre>
 
HTTP/1.1 200 OK
 
HTTP/1.1 200 OK
Line 48: Line 46:
 
[Response Body]
 
[Response Body]
 
</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 web client of the allowed domain 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 a '*' to indicate that all domain 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.'''
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....
+
 
 +
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 ==
 
== Risk ==
''A reminder : Into this article we focus on web application side because it's the only part in which we have full control.''
 
  
The risk here 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 the computer can contains malware or another web client can be used like Curl,OWASP Zap Proxy,....
+
''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.''
 +
 
 +
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...
  
 
== Countermeasure ==
 
== Countermeasure ==
We must scrutiny the '''Origin''' value on server side.
 
  
To achieve it we will use JEE Web Filter that will ensure that the domain received is consistent with the HTTP request  domain source.
+
'''Option A: Use CORS authenticated request'''
 +
 
 +
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 each 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 possibility 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'''
 
'''Sample implementation: Filter class'''
 +
 
<pre>
 
<pre>
 +
 
import java.io.IOException;
 
import java.io.IOException;
import java.net.InetAddress;
+
import java.util.ArrayList;
import java.net.UnknownHostException;
+
import java.util.Collections;
 
import java.util.Enumeration;
 
import java.util.Enumeration;
 +
import java.util.List;
  
 
import javax.servlet.Filter;
 
import javax.servlet.Filter;
Line 93: Line 123:
 
  *  
 
  *  
 
  * This implementation has a dependency on EHCache API because<br/>
 
  * This implementation has a dependency on EHCache API because<br/>
  * it use Caching for reverse DNS resolving result in order to enhance performance.
+
  * it use Caching for blacklisted client IP in order to enhance performance.
 
  *  
 
  *  
 
  * Assume here that all CORS resources are grouped in context path "/cors/".
 
  * Assume here that all CORS resources are grouped in context path "/cors/".
Line 101: Line 131:
 
public class CORSOriginHeaderScrutiny implements Filter {
 
public class CORSOriginHeaderScrutiny implements Filter {
  
// Filter configuration
+
/** Filter configuration */
 
@SuppressWarnings("unused")
 
@SuppressWarnings("unused")
 
private FilterConfig filterConfig = null;
 
private FilterConfig filterConfig = null;
  
// Cache used to cache Domain's resolved IP address
+
/** Cache used to cache blacklisted Clients (request sender) IP address */
private Cache domainsIPCache = null;
+
private Cache blackListedClientIPCache = null;
 +
 
 +
/** Domains allowed to access to resources (white list) */
 +
private List<String> allowedDomains = new ArrayList<String>();
  
 
/**
 
/**
Line 117: Line 150:
 
// Get filter configuration
 
// Get filter configuration
 
this.filterConfig = fConfig;
 
this.filterConfig = fConfig;
// Initialize Domain IP address dedicated cache with a cache of 15 minutes expiration delay for each item
+
// Initialize Client IP address dedicated cache with a cache of 60 minutes expiration delay for each item
 
PersistenceConfiguration cachePersistence = new PersistenceConfiguration();
 
PersistenceConfiguration cachePersistence = new PersistenceConfiguration();
 
cachePersistence.strategy(PersistenceConfiguration.Strategy.NONE);
 
cachePersistence.strategy(PersistenceConfiguration.Strategy.NONE);
CacheConfiguration cacheConfig = new CacheConfiguration().memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.FIFO).eternal(false).timeToLiveSeconds(900).statistics(false)
+
CacheConfiguration cacheConfig = new CacheConfiguration().memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.FIFO)
.diskExpiryThreadIntervalSeconds(450).persistence(cachePersistence).maxEntriesLocalHeap(10000).logging(false);
+
.eternal(false)
cacheConfig.setName("DomainsCacheConfig");
+
.timeToLiveSeconds(3600)
this.domainsIPCache = new Cache(cacheConfig);
+
.diskExpiryThreadIntervalSeconds(450)
this.domainsIPCache.setName("DomainsCache");
+
.persistence(cachePersistence)
CacheManager.getInstance().addCache(this.domainsIPCache);
+
.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");
 
}
 
}
  
Line 136: Line 177:
 
public void destroy() {
 
public void destroy() {
 
// Remove Cache
 
// Remove Cache
CacheManager.getInstance().removeCache("DomainsCache");
+
CacheManager.getInstance().removeCache("BlackListedClientsCache");
 
}
 
}
  
Line 145: Line 186:
 
*/
 
*/
 
@Override
 
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)  
 +
        throws IOException, ServletException {
 
HttpServletRequest httpRequest = ((HttpServletRequest) request);
 
HttpServletRequest httpRequest = ((HttpServletRequest) request);
 
HttpServletResponse httpResponse = ((HttpServletResponse) response);
 
HttpServletResponse httpResponse = ((HttpServletResponse) response);
Enumeration<String> headerNames = httpRequest.getHeaderNames();
+
List<String> headers = null;
 
boolean isValid = false;
 
boolean isValid = false;
 
String origin = null;
 
String origin = null;
String headerName = null;
+
String clientIP = httpRequest.getRemoteAddr();
String domainIP = null;
+
  
/* Step 1 : Retrieve the value of the "Origin" HTTP request header */
+
/* Step 0 : Check presence of client IP in black list */
while (headerNames.hasMoreElements()) {
+
if (this.blackListedClientIPCache.isKeyInCache(clientIP)) {
headerName = headerNames.nextElement();
+
// Return HTTP Error without any information about cause of the request reject !
if ("origin".equalsIgnoreCase(headerName)) {
+
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
origin = httpRequest.getHeader(headerName);
+
// Add trace here
break;
+
// ....
}
+
// Quick Exit
 +
return;
 
}
 
}
  
/* Step 2 : Perform analysis */
+
/* Step 1 : Check that we have only one and non empty instance of the "Origin" header */
// Origin header is required
+
headers = CORSOriginHeaderScrutiny.enumAsList(httpRequest.getHeaders("Origin"));
if ((origin != null) && !"".equals(origin.trim())) {
+
if ((headers == null) || (headers.size() != 1)) {
try {
+
// If we reach this point it means that we have multiple instance of the "Origin" header
// Remove HTTP / HTTPS protocols
+
// Add client IP address to black listed client
origin = origin.toLowerCase();
+
addClientToBlacklist(clientIP);
origin = origin.replaceFirst("http://", "");
+
// Return HTTP Error without any information about cause of the request reject !
origin = origin.replaceFirst("https://", "");
+
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
 +
// Add trace here
 +
// ....
 +
// Quick Exit
 +
return;
 +
}
 +
origin = headers.get(0);
  
// Get IP address of the specified domain
+
/* Step 2 : Check that we have only one and non empty instance of the "Host" header */
if (this.domainsIPCache.get(origin) != null) {
+
headers = CORSOriginHeaderScrutiny.enumAsList(httpRequest.getHeaders("Host"));
// First using Cache
+
if ((headers == null) || (headers.size() != 1)) {
domainIP = (String) this.domainsIPCache.get(origin).getValue();
+
// If we reach this point it means that we have multiple instance of the "Host" header
} else {
+
// Add client IP address to black listed client
// Second using reverse DNS and update Cache
+
addClientToBlacklist(clientIP);
InetAddress clientAddress = InetAddress.getByName(origin);
+
// Return HTTP Error without any information about cause of the request reject !
if (clientAddress != null) {
+
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
domainIP = clientAddress.getHostAddress();
+
// Add trace here
Element cacheElement = new Element(origin, domainIP);
+
// ....
this.domainsIPCache.put(cacheElement);
+
// Quick Exit
}
+
return;
}
+
}
  
// Compare IP addresses : Specified domain IP address against HTTP request sender IP address
+
/* Step 3 : Perform analysis - Origin header is required */
if ((domainIP != null) && domainIP.equals(httpRequest.getRemoteAddr())) {
+
if ((origin != null) && !"".equals(origin.trim())) {
isValid = true;
+
if (this.allowedDomains.contains(origin)) {
}
+
// Check if origin is in allowed domain
}
+
isValid = true;
catch (UnknownHostException uhe) {
+
} else {
// We print stack trace here for sample but in real app. a notification must be sent
+
// Add client IP address to black listed client
// to monitoring system in order to log malicious request...
+
addClientToBlacklist(clientIP);
uhe.printStackTrace();
+
isValid = false;
 +
// Add trace here
 +
// ....
 
}
 
}
 
}
 
}
  
// Finalize request next step
+
/* Step 4 : Finalize request next step */
 
if (isValid) {
 
if (isValid) {
 
// Analysis OK then pass the request along the filter chain
 
// Analysis OK then pass the request along the filter chain
Line 208: Line 258:
 
}
 
}
  
 +
/**
 +
* 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>
 
</pre>
 +
 +
  
 
'''Note:'''
 
'''Note:'''
Line 219: Line 295:
 
perform a regular automatic first level check (do not replace an manual audit and manual audit must be also conducted regularly).
 
perform a regular automatic first level check (do not replace an manual audit and manual audit must be also conducted regularly).
 
</pre>
 
</pre>
 +
 +
  
 
== Informations links ==
 
== Informations links ==
 +
 
* W3C Specification : http://www.w3.org/TR/cors/
 
* W3C Specification : http://www.w3.org/TR/cors/
 
* Mozilla Wiki : https://developer.mozilla.org/en-US/docs/HTTP_access_control
 
* Mozilla Wiki : https://developer.mozilla.org/en-US/docs/HTTP_access_control
 
* Wikipedia : http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
 
* Wikipedia : http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
 +
* CORS Abuse : http://blog.secureideas.com/2013/02/grab-cors-light.html
 +
  
 
[[Category:OWASP Java Project]]
 
[[Category:OWASP Java Project]]
[[Category: Injection]]
 
 
[[Category:Attack]]
 
[[Category:Attack]]
 
[[Category:Injection Attack]]
 
[[Category:Injection Attack]]

Latest revision as of 01:05, 17 August 2013

Last revision (mm/dd/yy): 08/16/2013

Contents

Introduction

CORS stands for Cross-Origin Resource Sharing.

Is a feature offering the possbility 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 role of the Origin header in exchange between web client and web application.

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

  • Step 1 : Web client send request to get 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 inform is source domain using the HTTP request header "Origin".

  • Step 2 : Web application respond 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 web client of the allowed domain 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.

  • 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 : Into this article we focus on 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...

Countermeasure

Option A: Use CORS authenticated request

In this option, we enable authentication on the resources accessed and require that the user/application credentials be passed with the 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 each incoming HTTP CORS requests:

  1. Have only one and non empty instance of the origin header,
  2. Have only one and non empty instance of the host header,
  3. 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,
  4. 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.
In a Enterprise inter application communication it's possible to add a check on the client IP range. 
"Business to business" communication offer possibility to for each part to indicate to others parts the IP range that it will 
be used by its applications.

Sample implementation: Filter class


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;
	}
}


Note: W3AF audit tools (http://w3af.org) contains plugins to automatically audit web application to check if they implements this type of countermeasure.

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).


Informations links