In our Rails application, it is common for users to keep multiple browser tabs open for hours or days at a time. The problem occurs when in one of these tabs, the user logs out then logs back in (or the session expires and a new session is created).
This causes the CSRF authenticity tokens on all the other tabs to become invalid. If they try to submit any form or make any ajax request on those tabs without refreshing, they will get an error (and in fact get logged out because that is the default Rails behavior when a bad authenticity token is passed).
This behavior is clearly undesirable. I was wondering what people do to gracefully handle situations where a user has a window open to your site but the authenticity token is out of date.
What I don't want to do is just redirect them to the login page, because then they might lose their work, if for example they have been writng a long blog post or something.
The solution that comes to mind is to have some javascript that either polls the server to check whether the authenticity token has changed, or polls the user's cookies to check whether the session has changed. I have never heard of anyone doing either of these, so I wanted to see what the community thought.
First of: logging in/out/in won't lead to appearing a new csrf-token. It still will be saved in the user's cookie. Next time it logs in via the same browser it'll get the same token.
In latest versions of Rails no errors will be thrown in the case of incorrect token: all the Rails does -- just resets the session before passing it to a controller.
So, update your Rails and you'll get one pain less.
Are you sure you are talking about CSRF token and not session token? It does not make any sense at all to redirect to login on a CSRF token mismatch. You just tell the user to repeat whatever he tried to do. (In a traditional web application this typically comes up when a form is submitted; you can treat the CSRF mismatch as a validation error, and show the form again, keeping all the field values, and ask the user to resubmit. In a more AJAX-heavy application you can use some sort of generic CSRF flag in the response, and if it is set, ask the user to do whatever he did (press the button etc) once more, or even automate the whole thing without bothering the user.
Related
I have a fully working product on Rails 5. I now wish to make a Chrome extension, using which users can create an 'Article'.
However, requests from my Chrome extension will be treated as Cross Site by my rails app. Hence, I was thinking of not doing the CSRF check at all on just my create action.
What is the biggest security risk associated with this? I understand after this, anyone will be able to make POST request to my server that creates a new article - however, this is not a damaging action like update, or worse, delete.
The Rails guide states that,
CSRF attack method works by including malicious code or a link in a
page that accesses a web application that the user is believed to have
authenticated. If the session for that web application has not timed
out, an attacker may execute unauthorized commands.
If a CSRF token is a valid one, it is a kind of assurance that the user session has not been hijacked and the request has been made with the user consent.
For more info, I recommend you to refer the Rails guide http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
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.
This sounds a bit evil, bear with me though. It's also not specifically a Rails question even though the two sites in question use Rails. (Apologies in advance for both these things)
Imagine two websites which both use Ruby on Rails:
mysite.com, on which i'm a developer and have full access in terms of changing code etc, and also have an admin login, so I can manage user accounts.
theirsite.com, on which i have an admin login but no dev access. I know the people who run it but i'd rather not ask them any favours for political reasons. That is an option however.
Using my admin login on each site i've made a user account for the same person. When they're logged into mysite.com, i'd like to be able to provide a button which logs them straight into theirsite.com. I have their username and password for theirsite.com stored in their user record in the mysite.com database, to facilitate this. The button is the submit button for a form which duplicates the form on the theirsite.com login page, with hidden fields for their username and password.
The stumbling block is that theirsite.com handles CSRF with an authenticity_token variable, which is failing validation when the login submits from mysite.com.
My first attempt to get past this was, in the mysite.com controller which loads the page with the form, to scrape the theirsite.com login page to get an authenticity token, and then plug that into my form. But this isn't working.
If i load the theirsite.com login page, and the mysite.com page with the remote login button in two browser tabs, and manually copy the authenticity_token from the theirsite.com form to the mysite.com form, then it works. This is because (i think) the authenticity_token is linked to my session via a cookie, and when i do it all in the same browser the session matches up, but when i get the authenticity token from theirsite.com via scraping (using Nokogiri but i could use curl instead) it's not the same session.
Question A) So, i think that i also need to set a cookie so that the session matches up between the browser and the Nokogiri request that i make. But, this might be impossible, and exactly the sort of thing that the anti-CSRF system was designed to defeat. Is that the case?
Question B) Let's say that i decide that, despite the politics, i need to ask the owner of theirsite.com to make a small change to allow me to log our users into theirsite.com when we know their theirsite.com username and password. What would be the smallest, safest change that i could ask them to make to allow this?
Please feel free to say "Get off SO you evil blackhat", i think that's a valid response. The question is a bit dodgy.
A) No, this is not possible as CSRF Protection is made to protect from actions like these only. So "Get off SO you evil blackhat"
As per the question I'm assuming that theirsite.com is using Rails(v3 or v4)
B) The smallest change that you could ask them to do is to make a special action for you, so that you could pass user credentials from your back-end and the user will be logged in from their on.
That action will work something like this :
You'll have a special code which will be passed along the credentials so that the request is verified on their servers. That code can either be a static predefined code or it can be generated on minute/hour/day basis with the same algorithm on both sites.
The function that you'd be asking to make for you will be like this:
Rails v3 and v4:
This action will be POST only.
#I'm supposing 'protect_from_forgery' is already done in theirsite.com
class ApplicationController < ActionController::Base
protect_from_forgery
end
#changes to be made are here as follows
class SomeController < ApplicationController
skip_before_filter :verify_authenticity_token, only: [:login_outside] #this turns off CSRF protection on specific actions
def login_outside
if(#check special code here)
#Their login logic here
end
end
end
Check this link for further information on skipping CSRF protection in Rails
Rails 4 RequestForgeryProtection
This shouldn't be too hard to do.
You need to send an ajax GET request to their signup page, copy the authenticity_token with javascript, and then send an ajax POST to the actual log in route that creates a session with the right credentials and authenticity_token.
One tricky part is finding out their log in route. Try /sessions/new or perhaps they have the url in the form, so look at the html there. Good luck!
The other tricky part is knowing how the parameters are usually sent. Check out the form's html. If all the input tags have user_ before their name's then you'll need to structure your parameters similarly; i.e. user_email, user_password.
It's entirely possible to fetch the crsf token and submit your own form (because a log-in page is accessible to anyone!). However, it'll be difficult to know the details of their arrangement. The guessing and checking isn't too bad of an options (again, /sessions/new is how I route my log in; you should also try your route to see if they have a similar one.)
If that doesn't work, try taking a look at their github account! It's very possible they haven't paid $7 a month and it's open to the public. You will easily be able to view their routes and parameter parsings that way.
Good luck!
This is impossible. The anti-csrf works like you send cookie to an user, and inject token in form of hidden field into a form; if the token matches with cookie form post is accepted. Now if you run form on your side, you can't set the cookie (as the cookie can be only set in domain of its origin).
If there is just some particular action you want to perform on their site, you can get away with browser automation. (i.e. your run browser on your server-side, script the action and execute it).
As for B) safest and smallest change is contradiction :) Smallest change would be to create handler for POST request on their side where you'll send username and password (this handler HAS TO run over https) and it will create auth cookie on their side.
As for safest - the whole concept of storing encrypted (not hashed) passwords is questionable at best (would you like your site to be listed here http://plaintextoffenders.com/ ?). Also if user changes his password on their side you're screwed. Secure solution would be that you'll store just 3pty UserID on your side, and you'll send asymmetrically encrypted UserID with Timestamp to their side (you'll encrypt it with your private key). They'll decrypt it (they'll have to have public key), validate if timestamp is not to old and if not they'll create auth cookie for given user id. There are also protocols for that (like SAML).
A)
What you are trying to do is really a form of a CSRF attack.
The idea behind a cross-site request forgery attack is that an attacker tricks a browser into performing an action as a user on some site, as the user who is using the site. The user is usually identified by a session identifier stored in a cookie, and cookies are sent along automatically. This means that without protection, an attacker would be able to perform actions on the target site.
To prevent CSRF, a site typically includes an anti-CSRF token in pages, which is tied to the session and is sent along in requests made from the legitimate site.
This works because the token is unpredictable, and the attacker cannot read the token value from the legitimate site's pages.
I could list various ways in which CSRF protection may be bypassed, but these all depend on on an incorrect implementation of the anti-CSRF mechanism. If you manage to do so, you have found a security vulnerability in theirsite.com.
For more background information about CSRF, see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).
B)
The smallest change which theirsite.com could do is to disable the CSRF protection check for the login page.
CSRF protection depends on the unpredictability of requests, and for login pages, the secret password itself protects against CSRF. An extra check through an anti-CSRF token is unnecessary.
I'm finding myself in a situation where I could provide a much nicer user experience if I could disable CSRF token checking for an endpoint in my rails app.
The endpoint is a create action (routed to by POST /whatever), that's behind a devise :authenticate! filter.
Would I open myself up to any additional security risks by disabling the CSRF-protection for that specific endpoint, or can I safely rely on the authentication before_filter to stop the kind of malicious requests that the CSRF token protects against?
Following is a bit more detailed explanation as to why I want to do this if anyone is interested.
My use case is that I basically want to create something very similar to the Facebook likebutton, but this button (unlike the Facebook counterpart) is commonly going to occur multiple times on the same page.
The CSRF protection works fine except for the case where the user visits the page with empty cookies.
In this case rails generates a new session for each of the X number of requests since they are all cookie-less. And, of course, for each new session a new CSRF token is generated and returned in the response to the iframe.
Since the browser only keeps one cookie for the domain, any subsequent requests from each of the iframes will be mapped to the same session, and thus all of the CSRF tokens (except one) are invalid.
The mapping to a single session is nice since the user can be prompted to log in once, and then be mapped to the same log in for each of the subsequent buttons presses – without having to reload the page.
A compromise would be to respond with a 401 Unauthorized, but preserve the session of the rejected request (by overriding handle_unverified_request). This would trigger the sign in popup again, but this time an instant redirect occurs since the user is already signed in.
It would, of course, be best to avoid that flash of the sign in popup window, and thus I'd like to disable the CSRF protection all together for just the create action.
Authenticated requests are precisely what CSRF is about.
What CSRF means is that the attacker convinces the user's browser to make a request. For example you visit a page hosted by an attacker that has a form that looks like
<form action="http://www.yourapp.com/some_action">
#for parameters here
</action>
And some javascript on the page that auto submits the form. If the user is already logged in to your app, then this request will pass any cookie based authentication checks. However the attacker doesn't know the csrf token.
For an unauthenticated request, csrf serves no purpose - the attacker can just go ahead and make the request anyway - they don't need to hijack the victim's credentials.
So, short version: disabling csrf protection will leave you vulnerable to csrf style attacks.
What CSRF is really about is making sure the form contains a parameter that an attacker can't fake. The session is an easy place to store such a value but I imagine you could come up with alternatives. For example if the user can't control any of the parameters in the form, you could add another parameter which would be a signature of all the other parameters in the form (possibly with some sort of timestamp or nonce to prevent replay attacks). Upon receiving the request you can tell whether the request is from a form you generated by verifying the signature.
Be very careful about this sort of stuff as it is easy to get wrong (and even the big boys get it wrong sometimes.
I have been trying to work out if I can have a login form on my static homepage. I would like to have some static pages and it would be great to have a login form on them. I spent some time getting more familiar with the authenticity_token that is generated with form_tag and although I realize we want to generally check all requests that aren't GET requests I feel like it might be possible to leave it out for a login because we aren't trusting the user with anything until after they are logged in. If a malicious site tried to use CSRF at this point it would need to know the login and password at which point the user is compromised anyway.
I definitely don't want to open up any security holes in my application and I appreciate all that rails does to keep this working, but in this situation am I right to think I can just submit a form without the token?
The purpose of the token is so that data cannot easily enter your system unless it is coming directly from that form. When a user visits the site, they get a cookie that matches the token in the form. When the form is submitted, those tokens must match.
If someone can submit the data from outside the form, you are opening up the potential for a script to make continuous login attempts. It's fairly easy to write something that would automate going to your site, getting a cookie and logging in using the form, but it's definitely more work. Think of it as another layer of security that you can have without negatively affecting your users.