How to pass the CSRF token between rails applications - ruby-on-rails

Our rails3 application talks to another rails application exposed as REST functions. We get the following warning post migration to rails3 on all POST calls made to the REST services.
WARNING: Can't verify CSRF token authenticity
How should we pass the csrf token on the wire when we make a POST call to the REST services, the ApplicationController has protect_from_forgery method and the call also lands on the handle_unverified_request call. We use HTTP basic auth to authenticate and it seems to work fine. What should we do to resolve this issue.

Ok now I'll give it a shot to answer your question.
CSRF tokens are "session dependent" this means the user must share a session with the application he is communicating with. This means a request must have been made before he submits the actual form which is the case for standard HTML forms where the form is displayed before it is submitted so there's room to generate a CSRF token for this user.
Let's call the Application which serves the UI Button App1 and the one hosting the REST Service App2.
The User shares a session with App1 and gets the UI Button served by App1. Once the user clicks the button a request to App1 is made, and App1 makes a request to the REST Service of App2.
Conclusion: The user doesn't share a session with App2 but your App1 has a session with App2. This concludes in two things:
The User is not vulnerable to Cross-Site-Forgery on App2 because he doesn't have a session on that server. So if I post a something like <img src="http://app2.com/rest-service/destroy> here, App2 wouldn't recognise me because I don't share a session with App2 and nothing happens.
You can implement your own security measures on the REST Service to secure it from the public:
Authentication by HTTP Basic Auth
Authentication by an API Key
....
This means you can drop the CSRF protection on the REST Service and stay on the safe side as long as user's can't make direct calls via AJAX etc.
Addition
CSRF protects your site from submitted forms that do not come from your origin (actually this applies to POSTed forms only since they usually manipulate data.
The big scenario is: I'm the attacker and I'd like to update your users account name to "Mr. Potatoe" the way I'd do that is to place a hidden form on my website which gets POSTed to Yourdomain.com/account with a hidden field like account[name]="Mr. Potatoe". Now what happens without CSRF protection? My browser submits the form, and sends the authentication cookie with it and my name is now "Mr. Potatoe". What happens with CSRF protection? My browsers submits the form and sends the cookie but the form has no CSRF token so the request gets rejected. Now is there a way as attacker to get the CSRF token under these circumstances? The answer is no. Maybe you ask yourself....
What if I place a hidden iframe on my site which points to Yourdomain.com/account/edit and just copy the hidden field containing the token? - Answer: It won't work because of same origin policy, you can't read whats inside an iframe if its content doesn't come from your domain.
What if I make an AJAX call in the background to get the token? Answer: It won't work because using pure AJAX you are bound to the same origin policy as well.
Now let's get to the point: I can't send a hidden AJAX call from my page to yours to harm your user who is on my site.
What does it mean? You can implement a before filter which checks if request.xhr? is true or whether the user agent is something like "My REST Client XY" because this can't be forged by a cross site request in a >>browser<<. So if this is the case you can ignore/disable the CSRF protection.
By the way if you want to make an AJAX request within your site you can get the CSRF token like this:
var token = $('meta[name="csrf-token"]').attr('content');
See the Rails UJS Script: https://github.com/rails/jquery-ujs/blob/master/src/rails.js#L81
NOTE: this only protects you from cross site forgery and nothing else....

Related

How to protect against CSRF

How can I protect my website against Cross-Site Request Forgery attack?
I am visiting a "normal" website. (f.e. normal.php)
In the background it loads another website (f.e. victim.php/send_comment) where I'm already logged in.
The website fills the comment boxes of the victim.php with JS and automatically send the request.
In the web I always find the trick to use tokens against CSRF. But in this example, the website normal.php will get the token, when it loads the other website.
Am I misunderstanding how the token works? If not, how can i prevent my site from accepting this request?
The whole idea of CSRF is that you can't get victim.php/send_comment without a token from a previous page you've visited.
You form a "chain" of requests from your initial login until you get there, where each request is authorized by the previous one - unless you intercept the login page, there should be no way to forge requests.
The easiest and safest way of doing this is just using a web framework that handles CSRF for you. Doing it by hand is probably unnecessary and error-prone.

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.

Getting past anti-CSRF to log a user into a site when you know their username and password

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.

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.

Resources