Rails API design without disabling CSRF protection - ruby-on-rails

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.

Related

Why does rails not verify XHR requests for CSRF?

In:
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
Which determines cross site request forgery validation. The following function:
def non_xhr_javascript_response?
content_type =~ %r(\Atext/javascript) && !request.xhr?
end
Ends up meaning no XHR requests are validated for CSRF even if the token is invalid?
Why is this? Do XHR requests mean that CRSF doesn't need to be validated with Auth Token?
That isn't correct. Rails checks the CSRF token for all non get/head posts, whether they are ajax or not.
In addition, since rails 4.1 Rails also checks for a csrf token for non xhr GET requests with javascript format. This is to prevent information being leaked when accessed in a cross domain request via JSON-P requests. For xhr the browser will have already enforced cross domain restrictions. This is where the method you have found is used: to see if a request needs this extra check.
If you go back far enough in time, rails did use to exempt ajax requests from CSRF checks, because of the aforementioned browser imposed restrictions. However Rails only knows that a request is an ajax request because of the presence of an X-Requested-With header and it was found that this could be forged, so this was removed

Get around CSFR token for iOS app

I am developing an iOS app for a RoR api (my co-worker made it). I am trying to develop the login portion, but while testing the api in POSTMan, I noticed it requires a CSRF token. Is there a way to get around doing an api call to get the CSRF?
Side note: I am using AFNetworking 2.0
There are a couple things you can do:
You can launch a GET request before you do the post, and retrieve the sessions CSRF token. Then submit the POST form with an authenticity_token parameter as the proper CSRF token. You can embed the original token anywhere in the view with the rails helper form_authenticity_token, or just get it from the sign up form's hidden tag. (This is my favorite option)
You can make a secondary loggin-in action on your site that is actually a GET request in and of itself. It's not too dangerous to bypass the CRSF token here because anyone should have access to log in. This has the advantage of keeping CRSF for any other action you may need, but it wouldn't work for actions that need more security.
You can make your iOS page consist of a UIWebView. I'm not sure if this will suit your needs, but it would have the proper CSRF token and you can remove the UIWebView after submitting. It's kind of like option 1, but bulkier.
Good luck!
Easiest fix is to change the server side to not authenticate the CSRF token. Here's an example of using a different controller for your API.
class Api::BaseController < ApplicationController
skip_before_filter :verify_authenticity_token
end
In general, your API is either going to require authentication for API calls (in which case you should have your own authentication, or OAuth, or any number of authentication mechanisms) or isn't (in which case it's a publicly accessible API and CSRF doesn't matter). There a few other threads here and here that discuss it.
From another answer on SO (go upvote it!):
CSRF attacks rely on cookies being implicitly sent with all requests to a particular domain. If your API endpoints do not allow cookie-based authentication, you should be good.

Rails 4 skipping protect_from_forgery for API actions

I've been implementing a Rails 4 application with an API. I want to be able to call the API from mobile phones and the webapp itself. I came across this note while researching protect_from_forgery:
It's important to remember that XML or JSON requests are also affected and if you're building an API you'll need something like:
class ApplicationController < ActionController::Base
protect_from_forgery
skip_before_action :verify_authenticity_token, if: :json_request?
protected
def json_request?
request.format.json?
end
end
I was thinking of doing this, but I have some reservations/questions:
This solution seems to leave the CSRF hole open because now an attacker could craft a link with an onclick javascript that posts JSON?
Would checking for an API token be a reasonable substitute? i.e., what if instead of skipping the authenticity check, allowing it to fail the check and recover in handle_unverified_request if the api token is present and correct for current user?
Or maybe I should just make the webapp and mobile devices send the CSRF token in the HTTP headers? Is that safe? How would the mobile phone even obtain the CSRF token, given that it isn't rendering HTML forms to begin with?
Edit for clarification:
I am more concerned about the webapp user clicking a crafted CSRF link. The mobile users are authenticated, authorized, an have an API key, so I am not concerned about them. But by enabling CSRF protection for the webapp users, the mobile users are blocked from using the protected API. I want to know the correct strategy for handling this, and I don't believe the Rails documentation gives the right answer.
An attacker could CURL at your controllers all they like, but if your API requires authentication, they wont get anywhere.
Making the API consumers send a CSRF is not really what CSRF does. To do this you'd need to implement a type of knocking mechanism where your client hits an authorization endpoint first to get the code (aka CSRF) and then submit it in the POST. this sucks for mobile clients because it uses their bandwidth, power, and is laggy.
And anyway, is it actually forgery (i.e. the F in CSRF) if its an authorized client hitting your controller after all?
Sending the CSRF token in an HTTP header is indeed a common approach. It ensures that the client has somehow obtained a valid token. For example, a crafted CSRF link will be sent with credential cookies but the header will not include the CSRF token. Your own javascript on the client will have access to domain cookies and will be able to copy the token from a cookie to the header on all XHR requests.
AngularJS follows this approach, as explained here.
As for your first two questions:
This solution seems to leave the CSRF hole open...
Indeed, which is why you should not disable the CSRF token also in your API.
Would checking for an API token be a reasonable substitute? ...
Probably not. Take into consideration the following (from OWASP):
CSRF tokens in GET requests are potentially leaked at several locations: browser history, HTTP log files, network appliances that make a point to log the first line of an HTTP request, and Referer headers if the protected site links to an external site.
General recommendation: Don't try to invent the wheel. OWASP has a page called REST Security Cheat Sheet as well as the one I linked to before. You can follow the Angular approach (copying the token from a cookie to a header on each XHR request) and for regular non-ajax forms, be sure to use only POST and a hidden field as is normally done in CSRF protection of static server forms.

Is it safe to disable CSRF-protection for an authenticated POST endpoint in Rails?

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.

Work with authenticity token? Or disable it?

My mini-web-appliance will submit data samples to a RoR app, which will add them to a MySQL table.
I figured out how to form the POST data packet, but what I don't get is how to avoid the authenticity-token problem.
Is there a way for my little dumb client to grab the right token and send it back? (I'm guessing not, or it wouldn't be much of a security feature).
This is not a highly security-sensitive application, so should I just tell this page to ignore the authentity-token altogether?
It will hopefully be authenticated by the fact that each client (web appliance) logs in with a unique user ID and password, so it would be protected by the session ID.
If I'm using "loose" language, please feel free to correct me. I'm new to deploying sites.
Keb'm
If each client is authenticated then it's ok to disable the authenticity token, that said you should only disable it for that one action.
skip_before_filter :verify_authenticity_token, :only => :create
If each client is authenticated then it's ok to disable the authenticity token
This is only true if you're using another authentication mechanism than http cookies. Because
you've mentioned 'session_id', i assume this is not the case.
With a standard rails session_id cookie, the user_id stored in a session and this action
being accessible by a webbrowser, it will be exposed to csrf attacks.
The best strategy for api's is implementing a custom authentication mechanism, some sort of authentication token, which is send with every http header.
Then either change the csrf protection to null_session or if you are less paranoid disable
csrf protection entirely for your api request as described here
If you still want to stick with cookie based authentication for your api, you should set
the csrf authenitcation token with the first GET request into an extra cookie. Then you read this cookie and send it's token as 'X-CSRF-Token' header. Rails will check for this header in the protect_from_forgery method and as cookies cannot be read by 3d parties an attacker will not be able to forge this request.
#application_controller.rb
protect_from_forgery with: :exception
after_action :set_csrf
def set_csrf
cookies['X-CSRF-Token'] = form_authenticity_token if protect_against_forgery?
end
# request session and x-csrf-toke
# the cookies will be stored into cookie.txt
curl -c cookie.txt http://example.com
#curl post command
curl -H "X-CSRF-Token: <token>" -b cookie.txt -d '{"item":{"title":"test"}}' "http://example.com/items.json"
See :verified_request? method to see how rails check for request forgery.

Resources