Thursday, February 07, 2013

Grab a CORS Light


Many of you already know that any cross-site HTTP requests invoked from scripts running within a browser are restricted by the Same-Origin-Policy.  Basically this means that any cross-site HTTP requests, such as XMLHttpRequest, are only allowed to make requests to the same domain that the page was loaded from, and not to any other domains.  However, HTML5 has provided a way to allow browsers to make cross-site HTTP requests while controlling access by the web server.  The CORS standard added HTTP headers that allow servers to service resource requests based on permitted origin domains.  Browsers, in turn, support these new HTTP headers and enforce the restrictions set by the header responses.

So, let’s take a look at a typical cross-site HTTP request using two separate domains, foo.com and bar.com.  First, the client browser loads a page from www.foo.com.  This page includes Java Script that uses the XMLHttpRequest object to make a cross-site HTTP request with a parameter that specifies the target resource; in this case it’s http://www.bar.com/page.  
The client browser then establishes a connection to www.bar.com and makes a GET request via HTTP.  Cross-site HTTP requests support both GET and POST methods without having to perform a “preflighted” OPTIONS request, which is used to ensure the request is safe to send.  When the request is sent, an Origin http header is included with a value of the domain or site that served the original page, which in this case is http://www.foo.com.
 The www.bar.com web server can inspect the Origins header in the GET request to determine if the request is permitted.  If it is permitted (or more likely not checked), it responds with a CORS header response and the requested resource.  In this example, the www.bar.com website allows cross-site HTTP requests from any domain, which is indicated by the Access-Control-Allow-Origin: * response header. 
Basically, the Access-Control-Allow-Origin header tells the browser which origins are permitted to access the requested resource.  If the value is an * then any origin domain is permitted to access the resource.  If, however, the header in our example instead had a value of www.bar.com, then the XMLHttpRequest would fail, and the content from the request resource would not be available to the browser.
All the major browsers that support HTML5 support CORS.  However, IE9 supports CORS with a different object called XDomainRequest rather than XMLHttpRequest.
The beauty of CORS is that it is accessible via Java Script and basically executes without the users knowledge.  This means that while the user is viewing or interacting with a page, the Java Script code can be interacting and making requests with resources from many different sites.

So how can we make cross-site HTTP requests to resources that require authentication?  By default, the cross-site XMLHttpRequest does not send cookies or credentials with the request.  However, a specific flag is available in the XMLHttpRequest object that will cause the cross-site HTTP request to include HTTP cookies and HTTP authentication information. 
By setting the .withCredentials flag to true, the XMLHttpRequest will include any cookies available in the browser for the target domain.  However, this is only half of the story.  The server must also respond with an Access-Control-Allow-Credentials: true as a HTTP header in the response, otherwise, the browser will reject the response, and thus not make the response available to the invoking web content.

So, how can we abuse this?  Well, like most vulnerabilities, a lot depends on if the code is written with security in mind.  If developers don’t understand CORS, they may decide to go down the path of least resistance and allow any domain to make cross-site HTTP requests.  Here’s something else to consider.  Many organizations believe that their internal web applications are inherently safe because they are not accessible from the Internet.  So internal web applications are not coded with the same security-mindedness as may be done with those that are Internet facing.  Yet another possibility is that developers want to be able to perform cross-site HTTP requests between their internal domains.  The problem is, if cross-site HTTP requests are allowed from any domain, then this permits these requests to originate not just from any internal domain, but external domains as well.

Let’s assume that all the internal web applications include an HTTP response header of Access-Control-Allow-Origin: *.  Let’s also assume that some of the internal web applications require authentication to access specific resource, but also either never expire the session, or have a “remember me” feature that automatically authenticates the user.   Remember, these are internal web apps, so, we’re probably not too far off from reality.  The attack starts with an ex-employee, with some inside knowledge of the internal environment, sends a malicious link to a number of previous teammates.  Of course, they can’t help but click on the link, which takes them to an evil web site.  A page is loaded that includes some well-crafted Java Script, along with some entertaining content to keep the user occupied.
While the user is being entertained, the Java Script makes a cross-site HTTP request to an internal web site; in this case it’s http://www.foobar.com/page.  The ex-employee has included the withCredentials flag set to true so he can leverage any cookies that are already in the user’s browser to authenticate to the target site.
Since the response includes the HTTP header Access-Control-Allow-Origin: * the response is accepted by the victim’s browser, and the content of the request is available to the Java Script code running in the browser.
The ex-employee’s code can now perform other actions against the internal site, and can send data received from the internal server, back to his server on the Internet, all without the user noticing.
So it’s pretty clear that allowing cross-site HTTP requests from any domain is a bad idea and that it makes sense to set the Access-Control-Allow-Origin header to only those domains that are expected to perform cross-site requests.  However, these can be circumvented with a Man-in-the-Middle attack!  Remember that it is the browser that enforces the server restrictions set by the HTTP header, so if anyone can manipulate the responses back to the browser, then the restriction can be circumvented.  For instance, with Burp, you can setup automatic match/replace values using regular expressions.  This can be found under the Proxy tab, and then under the Options sub-tab, then scroll down to Match and Replace.   Replacing the value for the Access-Control-Allow-Origin header with an “*” will tell the browser that cross-site HTTP requests are permitted from any domain, thus any resource data sent to the browser will be accepted.
If the web server checks the Origin header in the request, this too can be manipulated with Burp, setting the value in the header to the same domain as the target resource.   Unfortunately, from an attacker’s perspective, you can’t set the Origin header with Java Script using <object>.setRequestHeader(<header>, <value>);.

HTML5 has really provided pen testers, and attackers, with a nice suite of features and functionality for attacking the client, and using that client to pivot into the internal network.  A great example of this is the Browser Exploitation Framework, or BeEF.  This is a great tool for exploiting browser vulnerabilities to perform client-side attacks and pivot into the internal network.  Many organizations have matured their perimeter security, but are still in the process of understanding the potential threats that are available through the browsers on their internal network.  It’s important that we know what the potential threats are so we can identify ways to mitigate the risks.

For more information on CORS, go to the Mozilla Developer Network at https://developer.mozilla.org/en-US/docs/HTTP_access_control, and the W3C at http://www.w3.org/access-control/.  For more information about HTML5 vulnerabilities, attacks, and filters, check out our open source project Securing HTML5 Assessment Resource Kit, or SH5ARK, at http://sh5ark.secureideas.net.

Tony DeLaGrange is a Senior Security Consultant with Secure Ideas.  If you are in need of a penetration test or other security consulting services you can contact him at tony@secureideas.com or visit the Secure Ideas - Professionally Evil site for services provided.

1 comment:

monsur said...

Note that the "Access-Control-Allow-Origin: *" header cannot be used in conjunction with "Access-Control-Allow-Credentials: true". The "Access-Control-Allow-Origin" header must an actual origin value when "Access-Control-Allow-Credentials" is set to "true".