How Does Rails Csrf Protection Work

How does Rails CSRF protection work?

The behavior was changed fairly recently but the documentation has yet to be updated. The new approach being used is to presume the session has been hijacked, and therefore to clear the session. Assuming your session contains the all-important authentication information for this request (like the fact you're logged in as alice) and your controller assures the user is authenticated for this action, your request will be redirected to a login page (or however you choose to handle a non logged-in user). However, for requests which are not authenticated, like a sign-up form, the request would go through using an empty session.

It seems this commit also goes on to close a CSRF vulnerability, but I didn't read into the details of that.

To obtain the old behavior, you would simply define this method:

def handle_unverified_request
raise(ActionController::InvalidAuthenticityToken)
end

You can read more about CSRF and other Rails security issues at the Ruby on Rails Security Guide.

Where does Rails 4 store the authentication token for CSRF protection?

The CSRF token is stored in the user's session (which is in a cookie by default, in Rails; encrypted cookie in Rails 4). It is additionally written into the page as both a <meta> tag (for use by Javascript libraries) via the csrf_meta_tags helper method, and in a hidden field in any forms generated by form_tag or form_for in the page.

Looking at this project, the reason the CSRF token doesn't appear is that the HTML is written with a literal <form> tag, rather than the form_for helper, which would include the CSRF token. Additionally, the csrf_meta_tags helper is not present in the layout, which is why the meta tag doesn't get written.

The form is hardcoded to post to <form action="post_transfer" method="post"> which should not be protected by CSRF protections, so this form should be CSRF-able, even though the view is marked as protect_from_forgery. The protected_post_transfer method isn't likely to accept even legitimate requests, since the authenticity token is never sent.

I suspect the instructors missed this, since the test would be to use the form legitimately (hitting the unverified endpoint, and letting it succeed), and then the student is instructed to attempt to CSRF against the protected endpoint (which will never pass muster anyway), so you end up testing two different things that produce the right results for the wrong reasons.

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

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.

Is the Rails default CSRF protection insecure?

You are entirely correct. Token based CSRF protection is the most common and as long as you test your application for XSS you should be fine.

There are cases where CSRF can still be stopped regardless of XSS. For instance, for your password change forum, require that they know the current password. A hacker will be unable to forge this request unless he knows the current password, a moot point.

Another method is to require a Captcha to be solve for that request. I recommend using reCapthca.

CSRF Protection with doorkeeper authorization rails

It's good to have CSRF attack protection in your app. In api you can add this token to header and pass it between frontend and backend. I used this guide from pragmatic studio to set it up in my application:

https://pragmaticstudio.com/tutorials/rails-session-cookies-for-api-authentication

However, I have no idea how will it work with doorkeeper (haven't used it).

Ruby on rails CSRF protection forms

I figured I should make this into a full fledged answer.

No, manually creating a <form></form> in Rails will not automatically insert the authenticity token. The token is only inserted when using a Rails form helper. These helpers allow you to specify the fields for a form without specifying the authenticity token; it will put the token field into the form automatically. For example:

<%= form_for @person do |f| %>
<%= f.label :first_name %>:
<%= f.text_field :first_name %><br />

<%= f.label :last_name %>:
<%= f.text_field :last_name %><br />

<%= f.submit %>
<% end %>

Generates the following HTML with the hidden authenticity_token field:

<form action="/people" class="new_person" id="new_person" method="post">
<input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
<label for="person_first_name">First name</label>:
<input id="person_first_name" name="person[first_name]" type="text" /><br />

<label for="person_last_name">Last name</label>:
<input id="person_last_name" name="person[last_name]" type="text" /><br />

<input name="commit" type="submit" value="Create Person" />
</form>

But if you manually generate the HTML, for example using HAML:

%html
%head
%body
%form

The generated HTML looks like:

<html>
<head></head>
<body>
<form></form>
</body>
</html>

...without any authenticity_token field.

In the article you linked to, it says "A typical form generated in Rails..." meaning generated using a Rails form helper. Manually created forms are not "generated" in the sense used in this context.



Related Topics



Leave a reply



Submit