I have been reading on Rails security for hours now and I am a bit puzzled about the CSRF mechanism. It is stated everywhere I look that in order to switch it on you need to use the protect_against_forgery function.
I am working in a big application (Rails3.2-4) and can't seem to find any use of it in the codebase. Still the CSRF mechanism seems to be on:
protect_against_forgery? # true
What's the official answer from Rails on the matter? Do we need to set it explicitly or is it automatically set?
Yes, forgery protection is on by default. Should be enabled somewhere in your ApplicationController. Possibly in form of before_filter :verify_authenticity_token or something like that.
In the application.html.erb , we have csrf_meta_tags that are used to prevent the forgery request, if you have removed that tags , then you will get Invalid token authenticity error.
It is enabled by default.
Related
I building a REST API based on Rails 4.
To prevent from cross-site request forgery attacks, I added a CSRF token inside a custom HTTP header that is needed to perform requests such as POST, DELETE, etc.
I know Rails 4 also provides a protect_from_forgery method with a special option for APIs: with: :null_session.
So I think that, given it's a best practice, this new Rails method should be present at the top of my ApplicationController.
But in the same time, I'm also wondering why I should add it... if it is not necessary. Because as I said, my requiring a CSRF token inside a custom HTTP header.
Could you give me the benefits of adding this Rails feature? Thanks a lot.
protect_form_forgery just adds a before action to the controller which checks if the authenticity_token is valid.
The :with parameter specifies how the controller should behave if the token is invalid.
with: :exception: raises an exception in the controller which can by catched.
with: :null_session: resets the session itself. This means the complete session will be deleted. In other words the session cookie will be reset. For example an user_id stored in the session won't be available anymore (puts session[:user_id] # => nil). So you always have to provide a token or any other authentication, which is perfectly fine for an API.
You can also remove protect_from_forgery if you don't use session.
Inside one of my controllers, I write the following to protect certain pages from CSRF.
protect_from_forgery :only => [:foo, :bar]
When I load the URL's which correspond to foo and bar, and I view the HTML, I do not see any hidden input fields or meta tags which contain any security tokens, as described here.
However, during testing, I did observe that CSRF is not effective against these pages, although it is effective against other pages in the same application which are not protected.
So where does Rails 4 store the security token which is used for verifying that the request came from the original page?
Note that I have already read through the Ruby On Rails Security Guide, and from the section on protect_from_forgery, it says
This will automatically include a security token in all forms and Ajax
requests generated by Rails. If the security token doesn't match what
was expected, the session will be reset.
The problem is that this security token appears to be missing from the forms on the pages with CSRF protection enabled, even though CSRF is indeed not effective against them.
Note, this code is from a class project, in which one of the objectives is to perform a clickjacking attack to bypass the CSRF project. The question I am asking here is orthogonal to the purpose of the assignment.
I am simply curious about exactly how Rails does CSRF.
After doing rails server in the directly, the relevant URL which I cannot find the security token for is http://localhost:3000/protected_transfer.
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 would people skip the verification and increase the security vulnerability of their app? Is it beneficial to disable it on pages that have only GET requests? Thanks in advance.
CRSF check is already skipped for GET request in rails
http://guides.rubyonrails.org/security.html
3.1 CSRF Countermeasures
— First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF.
You can see the method itself as well.
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-verify_authenticity_token
.... Also, GET requests are not protected as these should be idempotent. ....
verified_request?()
Returns true or false if a request is verified. Checks:
is it a GET request? Gets should be safe and idempotent
If you have cross domain application you could have errors with authtoken verifying and you can disable it, but of course your application won't be secure. In rails 3 there are special methods for cross domain solution in out of box
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.
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