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.
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
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
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.
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.
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.
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.
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>,
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.
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,
Security Consultant with Secure Ideas. If you are in need of a
penetration test or other security consulting services you can contact him at
firstname.lastname@example.org or visit the Secure Ideas – Professionally Evil site for services provided.