Google

Cross Origin Request Forgery Pt 2 – Exploiting Browser Security

Written on:October 21, 2012
Comments are closed

Introduction

In my previous post I demonstrated how insecure handling of CSRF tokens by applications that switch between HTTP and HTTPS can put users at risk of request forgery attacks via man-in-the-middle interception. Today I ‘ll expand upon this point and demonstrate how it can provide remote attackers the same opportunity, if the user’s browser is not configured to prevent insecure cross-domain communications.

Recall in the last post, the site Etsy.com passed its session-based CSRF token via HTTP and later used that same token to validate function calls over HTTPS. If an attacker can access that token via HTTP, they may be able to initiate CSRF attacks to attack secure functions. As demonstrated, grabbing the CSRF token via MITM is trivial, but it limits the scope of the attack to targeting users on the same network. To expand the scope of this attack, a remote user could be tricked into making an HTTP request on behalf of the attacker via XMLHttpRequest() with the resulting response providing direct access to the CSRF token. The problem with this scenario is a cross-domain attack is largely hindered by the same-origin policy.

The same-origin policy concept is an important security control in modern-day web browsers designed to protect against one domain improperly accessing scripts and data from another. To enforce this security, the browser checks the domain name, protocol (HTTPS/HTTP) and port number* before granting access to a requested resource. If all three match between the requesting site and the requested resource, they are considered to be of the same origin and access is allowed.

While there are several approaches to relaxing the same-origin policy between domains, many require cooperation from both communicating parties which rules out one-way attack vectors. However, there are ways to relax this security feature at the browser level and Internet Explorer makes this particularly easy. It can be accomplished by changing the security setting “Access Data Sources across domains” from “Disable” (default) to “Enable”.

While this still won’t allow mixed protocol requests (HTTP to HTTPS), it will allow requests from different domains (http://www.abc.com to http://www.xyz.com) using XMLHttpRequest. 

You might say “If same-origin/cross-domain security is such a fundamental component of today’s web security why would anyone enable this setting?”  For starters, there are several enterprise products that may require this security setting to prevent errors, including IT Analytics for Symantec Endpoint Protection, LexisNexis Pay@Work, IBM Tivoli, Oracle’s Siebel Business Analytics, and others.  In addition, many public websites and universities require end-users to enable this security setting for applications to function properly.  Of course, there’s also the possibility that an end-user has enabled this setting accidentally.  Therefore, it’s clear that such a user-controlled browser setting is not a completely reliable security control. 

Assuming this setting has been enabled, let’s take a look at how an attacker can initiate a Cross-domain XMLHttpRequest() to take advantage of a site’s insecure handling of CSRF tokens, once again using Etsy.com as an example**. 

Scenario

First, an authenticated Etsy.com user is persuaded to visit a malicious page controlled by attacker. In this scenario, the user is tricked into believing they have been selected to test new personalized features of Etsy.com, called “MyNewEtsy”. To make it appear authentic, the attack could employ the use of Etsy.com’s public CSS for formatting, actual Etsy.com links for access, and could even be hosted on an available domain such as “MyNewEsty.com”. 

phishing message

Similar to the last blog post, the malicious page visited by the unsuspecting user contains hidden forms that execute secure, user-restricted Etsy.com functions that normally require a CSRF token to validate the authenticity of the request.  For example, the following hidden form is used to change the user’s privacy settings (actual input names/values have been altered), with a corresponding hidden iframe used as the target.  Note that the value for input id _nonce has not yet been populated.

In order to get the CSRF token needed to populate the hidden form value, the malicious page attempts to make a cross-domain call to Etsy.com on behalf of the user via XMLHttpRequest(). Remember, this is possible because Etsy.com passes its CSRF token over HTTP.

request = new XMLHttpRequest();
request.onreadystatechange=function(){
if (request.readyState==4 && request.status==200){
data=request.responseText;
var csrfnonce = data.match(/meta name="csrf_nonce".*/g)[0].split("\"")[3];
if (csrfnonce == "") {
displayLoginSplash();alert ("You must log in to see the new personalized features! Return to this page after login");
var win = window.open("https://www.etsy.com/signin","","directories=0,location=0,menubar=0,status=0,toolbar=0,height=750");
}else{
document.getElementById("_nonce").value = csrfnonce;
submitattack();
}
}
}

try {
request.open("GET", "http://www.etsy.com", true);
request.send();
}
catch(e)
{
displayError();
}

If the XMLHttpRequest() is successful, we can assume the user’s browser is configured to allow Cross-domain requests.  The next step is to see if the user is logged in by parsing the returned response and grabbing the CSRF nonce. This can be accomplished using a simple data.match() regex, as illustrated above.

If the CSRF nonce value is not NULL, we can also assume the user is authenticated to Etsy.com and set the value of the hidden form input _nonce with the obtained CSRF nonce using document.getElementById(), as illustrated above.

Now the hidden form is now fully populated and we have all of the required validators (session cookies submitted by user + CSRF token) to make the request and change the user’s privacy settings without their knowledge.

function submitattack(){
var attackform=document.getElementById('attackform');
attackform.setAttribute('target', 'attackframe');
attackform.submit();
}

As you can see by the line request.open(“GET”, “http://www.etsy.com”, true), regardless of whether or not the attack is successful, the malicious page immediately displays the real Etsy.com homepage when it loads.  Everything else happens in the background without the user’s knowledge.  As long as they were expecting to visit Etsy.com there is no indication to the user that anything is amiss. 

You may have also noticed two additional functions in the example code: displayLoginSplash() and displayError().  Both functions are designed to trick the user into facilitating a successful attack should certain errors occur.  The former (displayLoginSplash()) is executed only if the variable csrfnonce obtained from XMLHttpRequest() has no value, indicating that the user is not authenticated to Etsy.com.  If this function is triggered, it uses the stylesheet used by Etsy.com to format an authentic-looking error page.  The fake error page instructs the user they must first log in to access these new features and then opens the actual Etsy.com login form in a new window.

Fake authentication error

Directing the user to login before executing the malicious code

The latter function (displayError()) is triggered only if the XMLHttpRequest is unsuccessful, which would indicate that the user’s browser is not configured to allow Cross-domain requests.  Should this occur, the function once again calls upon Etsy.com CSS to format an authentic-looking error in an attempt to trick the user into enabling this security setting.

Fake cross-domain error

Convincing the user to change their security settings if they are not set already

Should this example attack execute successfully, the user’s Privacy settings will be changed to the least restrictive possible without their knowledge, as shown below. 

Altered Privacy Settings

As in the prior blog post, this attack scenario can be expanded to include additional secure function calls that would normally require a CSRF token, such as sending messages on a user’s behalf.  This scenario is still somewhat limited in scope in that it relies on an authenticated user to visit the malicious page with cross domain access enabled in their browser security settings.  However, it does further illustrate the need to properly protect CSRF tokens and the dangers of relying on browser-level security measures. 

Prevention

What can be done to prevent such an attack? Once again, I would assert it’s a shared responsibility between application developers and users.

Application Developer

Never assume a user’s browser will provide absolute protection against such an attack.  The prevention mechanisms outlined in my previous post still apply.  Namely, don’t pass CSRF tokens used to validate secure function calls over HTTP and, if possible, check the origin of sensitive function calls to ensure they follow the intended logic of your application.  Of course, the primary preventative measure is to implement HTTPS across your entire site. 

Users

Again, be especially conscious of using sites that switch between HTTP and HTTPS.  At the time of this post, Etsy.com has implemented additional security features including providing the users the option to only navigate the site over HTTPS (highly recommended).   Also, check your browser security settings and never make changes unless you understand their impact.  Internet Explorer allows for setting changes to be confined within security zones so if a corporate application requires enabling cross-domain access, you may only have to change it for the Local Intranet zone. 

* Internet Explorer no longer considers the port to be a part of the Security Identifier (origin) used for Same Origin Policy enforcement.

** Once again, I did report this issue to Etsy.com under its bug bounty program responsible reporting guidelines but was informed it was not considered a qualifying vulnerability. Nevertheless, knowing the Etsy.com was about to offer complete HTTPS browsing as an option to its users, I waited until that feature was implemented before I published this post. That feature has now been in place for a few weeks at the time of this posting.

Sorry, the comment form is closed at this time.