rails csrf token lifetime - ruby-on-rails

I use Rails request_forgery_protection mechanism to protect my POST actions from CSRF attacks and captcha to protect the GET actions. This way if someone stages a two-phase attack within one session, GET-ting the form with the current token and then POST-ing a forged request with that token, he will eventually be faced with a captcha check.
I'm stuck with that though, because Rails doesn't regenerate the CSRF token until the end of session. That doesn't seem right to me, I'd think the token should be renewed before the next action. I'm wondering maybe I have tweaked something wrong? Is there another way of doing this?
Thanks.

I've not sure if this is a good idea or not, but you can nil out the token yourself on get requests from inside your application controller.
before_filter :reset_csrf
private
def reset_csrf
session[:_csrf_token] = nil if request.get?
end

In case form token is NOT regenerated for each page request, this protection is bad. I faced it sometime ago (when testing Redmine, which is RoR-based) and reported this issue, but didn't retested it.
If it's still not regenerated, I suggest you report this to RoR team.

Related

How is it possible for a legitimate user to submit an invalid CSRF token in Rails?

Our error logs occasionally contain legitimate form submissions that cause ActionController::InvalidAuthenticityToken errors.
My hypothesis is that the CSRF token stored in the user's session cookie has changed at some point after the form was loaded but before it was submitted. This causes a mismatch between the POSTed token and the token in the cookie, leading to this error.
Given that a Rails session cookie expires only when the browsing session ends (ie when the web browser is closed), what are the ways in which this cookie (and the CSRF token it includes) can be changed without closing the browser?
We are using cookies to store session data, which is Rails' default behaviour.
The user could have logged out and back in, but had a tab with a form open from the old session. That would send the old token.
Here's what we know:
Legitimate form submissions that cause an InvalidAuthenticityToken exception have somehow lost their original CSRF token.
The token is kept in the session, which is kept in an encrypted cookie. So this error means their session cookie has changed since the form was generated.
The session cookie does not expire unless the user's browser window is closed.
I now believe there are two ways that invalid CSRF tokens can be submitted by legitimate users.
Note that these apply specifically to Rails 4.2. As I understand it, the "per-form CSRF tokens" feature in Rails 5 may mitigate them.
1. Session change in another tab
As per #crazymykl's answer, the user could open the form, then log out in another tab. This would cause the session cookie stored in the user's browser to change. When they came back to the original tab and submitted the form, the token from the form would not match the token in the session, and the error would pop.
2. Caching
As per this rails bug, Safari behaves oddly with caching under some circumstances. Telling it to reopen with the same windows as last time (via Safari > Preferences > General), opening the form and quitting Safari results in the form being redisplayed.
Submitting that cached form causes a CSRF error. As the opener of the bug concludes, it appears that Safari caches the page, but drops the session cookie. Hence the mismatch.
The solution to this problem is to set a Cache-Control header with the directive no-store. (Difference between no-cache and no-store explained here).
Further examples welcome :).
Are you using Ajax form to submit the request? Does your page has multiple forms? If so, check your code whether correct csrf token is submitted along with the request. We had similar issue that page is rendered with one csrf token and we used this token to submit form one and we would have got another csrf token but the second was sending old csrf token that result in error.
I am not sure how this is helpful but want to share similar problem faced by me
You wrote: Given that a Rails session cookie expires only when the browsing session ends (ie when the web browser is closed), what are the ways in which this cookie (and the CSRF token it includes) can be changed without closing the browser?
First off, your hypothesis is valid. How you got there might be worth considering, though.
The presumption you present needs focus on two levels.
One: a cookie stored does not get deleted when the web browsing session ends unless something has been coded that way; the cookie is likely persistent until he cookie timeout, so it's likely that the next access of the page will use the old token, but because developers generally allow the "new login" to refresh the page, they are likely to also refresh the token at that time. See #Shikhar-Mann response to better understand the sign_out_user.
Two: the cookie doesn't have to be changed for this problem, it's the mismatch of the CSRF token that is the issue.
So the root question should be: what are the ways that we can have a mismatched CSRF token, which would be easier to answer: old data on client due to a long wait, which causes a timeout on the server, which invalidates the CSRF token during the delay. If the web page is not configured/created to also time out and redirect, the client / user would never know.
Also, might I suggest that the CSRF NOT be persisted? It's really not valuable to do so if you can access form data; typically I create a hidden field with the CSRF data and use that to post back instead. CSRF doesn't live very long, and session data is made to persist.
If closing browser is not an option. Then you got to log out the user. To achieve that place the following code in ApplicationController.
rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # Example method that will destroy the user cookies
end
P.S: Above is from http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf so refer that for more information.

CSRF Token in Django and iOS

So I am trying to understand what to do here... I am doing a POST call to my Django server from iOS and I keep getting the 403 Error (Invalid CSRF Token). I am thinking about implementing a function that will return me the token (you will need to be logged in to access that function), and then add the token to my POST call.
Now... I don't understand what is the point of doing that? If I use TastyPie and the required login is APIKey... should I just exempt the csrf check?
To make sure I understand things right... is the CSRF generated per user session? Therefore, if I don't use Cookies, CSRF is not necessary?
How do people usually use their Django Servers with an iOS and making such POST calls?
Thanks!
You're right: if you don't use cookies to manage your sessions, you don't need CSRF protection. CSRF works because session cookies are automatically attached to the request; access tokens are not.
I personally found this article very useful. It is definitely worth reading, and would probably answer a lot of your questions.
As for tastypie: it allows SessionAuthentication. If you allow session authentication in tastypie, I suggest you look into a way to protect your users against CSRF. For other authentication schemes this doesn't seem necessary. As far as I know, Dmitry is right about tastypie disabling CSRF by default, which means it is strange that you get that 403 Error. Perhaps there is something else going on. Try wrapping the view in #csrf_exempt.
As for CSRF tokens, they are also called session independent nonces. They are meant to be permanent, but you probably know that is impossible for cookies. Anyway, this means that CSRF cookies persist through sessions.
You're right, CSRF does not make much sense in this case, because its purpose is to protect users from data tampering in a browser.
I believe that Tastypie disables CSRF on its views by default.

Switching out authlogic from rails app. Session is missing from post requests

I was using authlogic on my Rails app and decided to remove it in favor of implementing 3rd party authentication using OAuth. Switching to to this approach has left me with a very interesting problem:
The rails session object is empty when I make a post request from the app. A 'get' request always has the session populated but not a post. I can verify that it is indeed posting the session cookie in both cases but dont see why this is happening.
Any ideas why this is happening or tips to debug this?
This is because the posts were missing the csrf token. For some reason, Authlogic takes care of the CSRF verification for AJAX posts.

Rails API design without disabling CSRF protection

Back in February 2011, Rails was changed to require the CSRF token for all non-GET requests, even those for an API endpoint. I understand the explanation for why this is an important change for browser requests, but that blog post does not offer any advice for how an API should handle the change.
I am not interested in disabling CSRF protection for certain actions.
How are APIs supposed to deal with this change? Is the expectation that an API client makes a GET request to the API to get a CSRF token, then includes that token in every request during that session?
It appears that the token does not change from one POST to another. Is it safe to assume that the token will not change for the duration of the session?
I don't relish the extra error handling when the session expires, but I suppose it is better than having to GET a token before every POST/PUT/DELETE request.
Old question but security is important enough that I feel it deserves a complete answer. As discussed in this question there are still some risk of CSRF even with APIs. Yes browsers are supposed to guard against this by default, but as you don't have complete control of the browser and plugins the user has installed, it's should still be considered a best practice to protect against CSRF in your API.
The way I've seen it done sometimes is to parse the CSRF meta tag from the HTML page itself. I don't really like this though as it doesn't fit well with the way a lot of single page + API apps work today and I feel the CSRF token should be sent in every request regardless of whether it's HTML, JSON or XML.
So I'd suggest instead passing a CSRF token as a cookie or header value via an after filter for all requests. The API can simply re-submit that back as a header value of X-CSRF-Token which Rails already checks.
This is how I did it with AngularJS:
# In my ApplicationController
after_filter :set_csrf_cookie
def set_csrf_cookie
if protect_against_forgery?
cookies['XSRF-TOKEN'] = form_authenticity_token
end
end
AngularJS automatically looks for a cookie named XSRF-TOKEN but feel free to name it anything you want for your purposes. Then when you submit a POST/PUT/DELETE you should to set the header property X-CSRF-Token which Rails automatically looks for.
Unfortunately, AngualrJS already sends back the XSRF-TOKEN cookie in a header value of X-XSRF-TOKEN. It's easy to override Rails' default behaviour to accomodate this in ApplicationController like this:
protected
def verified_request?
super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
end
For Rails 4.2 there is a built in helper now for validating CSRF that should be used.
protected
def verified_request?
super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
end
I hope that's helpful.
EDIT: In a discussion on this for a Rails pull-request I submitted it came out that passing the CSRF token through the API for login is a particularly bad practice (e.g., someone could create third-party login for your site that uses user credentials instead of tokens). So cavet emptor. It's up to you to decide how concerned you are about that for your application. In this case you could still use the above approach but only send back the CSRF cookie to a browser that already has an authenticated session and not for every request. This will prevent submitting a valid login without using the CSRF meta tag.
Rails works with the 'secure by default' convention. Cross-Site or Cross-Session Request Forgery requires a user to have a browser and another trusted website. This is not relevant for APIs, since they don't run in the browser and don't maintain any session. Therefore, you should disable CSRF for APIs.
Of course, you should protect your API by requiring HTTP Authentication or a custom implemented API token or OAuth solution.

Rails, OAuth, and CSRF protection

I am using REST and OAuth to talk to a Rails app (from an iPhone app, but that should not be relevant). However, I am running into some issues with Rails' CSRF protection (via protects_from_forgery).
I understand that CSRF protection only kicks in for regular form submissions (i.e. Content-Type=application/x-www-form-urlencoded), so I would be fine if I was submitting JSON or XML data. Unfortunately, OAuth is currently limited to application/x-www-form-urlencoded requests. There's a draft spec that extends OAuth to non-form-urlencoded data, but this doesn't help me right now.
The way I see it, I have the following options:
Send the data as JSON, knowing that it would not be part of the OAuth signature and thus subject to man-in-the-middle attacks. Obviously not an attractive solution.
Create special Rails actions (e.g. UsersController#update_oauth) that internally delegate to the regular actions (e.g. UsersController#update). Then exclude these from the forgery protection (protects_from_forgery :only => [:update]). This should work and might be borderline acceptable for one or two actions, but obviously would be a very messy solution.
Override the Rails CSRF protection to ignore OAuth requests. I have not tried this, but it seems like it should be possible to change one of the hooks (perhaps the verify_authenticity_token filter) to consider OAuth requests successful.
Has anybody run into this before? Any recommendations? Or am I perhaps missing something basic?
I'll answer my own question. :)
I added the following method to our OAuth controller extensions. The only thing this adds on top of the default implementation is the oauth? check. This seems to do the trick and feels like a pretty clean solution.
def verify_authenticity_token
verified_request? || oauth? || raise(ActionController::InvalidAuthenticityToken)
end

Resources