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.

One Response to Analysis of John Wilander’s Triple Submit Cookies

  1. Nice insight in to attacking Triple Submit. And to me httpOnly didn’t make any sense either.
    I always wonder if only developers used PUT / DELETE XHR for state changing requests.

    The real problem in the security world is that its real hard to change people :)

Leave a comment