Why does the CSRF token in Rails not prevent multiple tabs from working properly?
Asked Answered
Y

1

22

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?

Yazbak answered 8/12, 2017 at 23:1 Comment(3)
Because the tokens are linked to a session identifier cookie. Resetting the session invalidates any session cookies held by the client. guides.rubyonrails.org/action_controller_overview.html#sessionLatoyia
@Latoyia do you mind explaining that in detail?Yazbak
Maybe this question has the same answer? #50160347Natalee
B
27

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.

Began answered 11/4, 2018 at 20:11 Comment(3)
Maybe this question has the same answer? #50160347, @Sarkom?Natalee
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?Sarcophagus
@Sarcophagus 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.Began

© 2022 - 2024 — McMap. All rights reserved.