21

After reading about how the CSRF protection works in Rails, I tried to trigger CSRF protection by doing this:

Note: We are using cookie based sessions.

  1. Visit login page. Check CSRF token in meta => abc123
  2. Open a 2nd browser tab, and visit the same login page. CSRF token in meta is different => def456
  3. Go back to 1st tab.
  4. Submit login credentials.

I expected this to fail, because the 2nd tab generated a new, different CSRF token. When the login form submits, shouldn't the token that gets submitted to the server be an old, stale one?

However, this does work:

  1. Visit login page. Check CSRF token in meta => abc123
  2. Open a 2nd browser tab, and visit the same login page. CSRF token in meta is different => def456
  3. Go back to 1st tab.
  4. Submit login credentials.
  5. Logout (clearing session)
  6. Go to 2nd tab, and submit login.

In this case, I get an InvalidAuthenticityToken exception as expected. Why?

you786
  • 3,659
  • 5
  • 48
  • 74
  • Because the tokens are linked to a session identifier cookie. Resetting the session invalidates any session cookies held by the client. http://guides.rubyonrails.org/action_controller_overview.html#session – max Dec 08 '17 at 23:35
  • 2
    @max do you mind explaining that in detail? – you786 Dec 09 '17 at 15:03
  • Maybe this question has the same answer? https://stackoverflow.com/questions/50159847/single-page-application-and-csrf-token –  May 06 '18 at 23:11

1 Answers1

26

Source: https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef

Why the request in the second tab doesn't fail

The CSRF token in the meta tag is actually a concatenation of two strings: a "one-time pad" generated per request, and the "real" CSRF secret XORed with the one-time pad. See in the diagram below how the one-time pad is prepended to the XORed string in the masked token, which gets stored in the meta tag:

Diagram of construction of CSRF token

Rails stores the CSRF secret in a session cookie without XORing. Javascript should be used in the browser to read the masked token from the meta tag and pass it in the X-CSRF-TOKEN header.

When Rails validates a request, it:

  1. Splits the value passed in the X-CSRF-TOKEN header to retrieve the one-time pad and XORed string.
  2. XORs them together to retrieve the real secret.
  3. Compares this with the secret in the cookie.

This is why you are seeing changing tokens in the meta tag -- the one-time pads are different. If you validated the tokens, you would find the same secret in both tokens.

Note: This one-time pad business might seem unnecessary. Anyone can retrieve the real secret if they have the masked token. Surprisingly, the purpose of the XORing is to change the CSRF token on every request so an attacker can't use timing attacks to discern the secret. See this paper on the BREACH SSL attack.

Why the request fails on logout

As noted in @max's comment, logging out deletes the session cookie. The next request generates a new CSRF secret which no longer matches the older masked tokens.

Sarkom
  • 823
  • 10
  • 12
  • Maybe this question has the same answer? https://stackoverflow.com/questions/50159847/single-page-application-and-csrf-token, @Sarkom? –  May 06 '18 at 23:11
  • Since the first step is to check that the `form_token` matches the `session_token` -- if my second tab updates my `session_token` (cookie), won't any requests on the old tab now fail because `session_token != form_token`? – Meekohi Apr 25 '19 at 20:37
  • @Meekohi Yes, but the only way to update the session_token is to logout. The cookie encodes more than just the session token, so the cookie can change in every request but not the session token. – Sarkom Apr 27 '19 at 00:09