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 (syntax corrections)
(Countermeasure B does not help AT ALL. Main recommendation here should be: Don't use the Origin header to validate the sender, as it is not reliable. Why are we recommending adding some overly-complicated mechanism that doesn't actually work?)
Line 65: Line 65:
  
 
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  
 
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  
Line 78: Line 76:
 
* The client application must store (in a secure way) the credentials to use,
 
* 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.
 
* 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 steps 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 non empty instance of the '''origin''' header,
 
# Have only one non empty instance of the '''host''' header,
 
# The value of the origin header is present in an internal allowed domains list (white list). This check is done before step 2 of the CORS HTTP requests/responses exchange process, and the allowed domains list will be provided to the client,
 
# Cache IP of the sender for 1 hour. If the sender sends any requests with an origin domain that is not in the white list then all requests will return an HTTP 403 response (protract allowed domain guessing).
 
  
 
We use the method above because it's not possible to be 100% certain that any request comes from an expected client application, since:
 
We use the method above because it's 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,
 
* 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.
 
* 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'''
 
 
<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>
 
 
 
  
 
== Informations links ==
 
== Informations links ==
Line 309: Line 87:
 
* 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
 
* CORS Abuse : http://blog.secureideas.com/2013/02/grab-cors-light.html
 
  
 
[[Category:Java]]
 
[[Category:Java]]
 
[[Category:Attack]]
 
[[Category:Attack]]
 
[[Category:Injection Attack]]
 
[[Category:Injection Attack]]

Revision as of 20:00, 25 February 2019

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


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

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 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 resquest/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.

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.

We use the method above because it's 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,
  • 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.

Informations links