Summary

The CSRF tokens generated by Spring are vulnerable to a BREACH attack. More details at http://breachattack.com/

I'll create a pull request with a proposed code change to fix this.

Actual Behavior

Spring always returns the same CSRF token to the browser.

Expected Behavior

The simplest way to mitigate this would be to return a token which is composed of a random per secret request XORed with an internal CSRF token. This effectively means that the browser receives a new CSRF token with each request.

Configuration

This only occurs when you turn on CSRF protection in Spring and also have HTTP compression enabled somewhere in your web server stack.

Version

Currently exists in latest version in Git.

Sample

N/A

Comment From: jgrandja

The proposed solution will be to introduce a default method to CsrfToken, as follows:

public interface CsrfToken {
    …
    default boolean matches(CsrfToken token) {
        return getToken().equals(token.getToken());
    }
}

Then override the default method in DefaultCsrfToken to handle the XOR logic.

Targeted for 5.0 RELEASE which will support Java 8.

Comment From: Wadeck

@jgrandja care about potential timing-attack with your proposal. Using MessageDigest.isEqual could be better at that point.

Comment From: rh-id

Hi @rwinch @jgrandja @eddumelendez do you mind if i take over this?

Comment From: rwinch

That would be great by me, but let's wait and get confirmation from @eddumelendez

A few notes for whomever implements this https://github.com/spring-projects/spring-security/pull/7221#issuecomment-536724408

Comment From: eddumelendez

@rh-id sure, please go ahead! 🙂

Comment From: sebtacke

This issue popped up in a whitesource scan for me and I must say the proposed solution feels like building a high security gateway on an open field. The CSRF protection requires session management, so the pages that have it will also have a session id cookie. If you enable HTTP compression for such a page an attacker can bruteforce the session id with a BREACH attack. Once the attacker has the session id, a randomized CSRF token won't do anything, because you can just call the page with the session id cookie and have a valid csrf token. The only working protection against BREACH is not enabling HTTP compression for any page/URL that returns user supplied input. The safest solution is limiting compression to static resources or disabling it completely.

Comment From: NicoleG25

Hey @jzheaux sorry to randomly pick you but you were the last one to commit to the repo 😄 Do you happen to know if there's any plan to mitigate this?

or maybe @rwinch can shed some light?

Comment From: rwinch

We don't have any plans around this issue at the moment. Protecting CSRF tokens from BREACH does not address BREACH in its entirety and leads to a false sense of security. BREACH impacts all secrets and CSRF tokens are just one type of secret that appears in a response. However, if there is authentication to a site there are presumably other pieces of information that are secret (i.e. a bank account number, email content, etc).

User's should prefer mitigating BREACH entirely rather than focusing so narrowly on a CSRF token. A more robust fix is

to disable HTTP compression whenever the referrer header indicates a cross-site request, or when the header is not present.[5][6] This approach allows effective mitigation of the attack without losing functionality, only incurring a performance penalty on affected requests.

For reference, the final proposed Apache setup looks like:

Here is my final setup, for futher reference:



        SetOutputFilter DEFLATE



        BrowserMatch ^Mozilla/4 gzip-only-text/html


        BrowserMatch ^Mozilla/4\.0[678] no-gzip


        BrowserMatch \bMSIE !no-gzip !gzip-only-text/html


        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|zip|gz|tgz|htc)$ no-gzip dont-vary


        # BREACH migitation


        SetEnvIfNoCase Referer .* self_referer=no


        SetEnvIfNoCase Referer ^https://www\.example\.net/ self_referer=yes


        SetEnvIf self_referer ^no$ no-gzip


        Header append Vary User-Agent env=!dont-vary


If running on non conventional port (not 443), this needs adustement:

        SetEnvIfNoCase Referer ^https://www\.example\.net:10443/ self_referer=yes

All that said, we still have plans to implement this at some point as defense in depth is always preferred. However, due to the reasoning above this issue has not been prioritized over other efforts.