Ruby on Rails by default encourages developers to develop insecure web applications. While it’s certainly possible to develop secure sites using the Rails framework you need to be aware of the issues at hand and many technologies that make Rails a powerful easy to use platform will work against you.
Cross Site Request Forgery
CSRF is the new bad guy in web application security. Everyone has worked out how to protect their SQL database from malicious input, and RoR saves you from ever having to worry about this. Cross site scripting attacks are dying and the web community even managed to nip most JSON data leaks in the bud.
Cross Site Request Forgery is very simple. A malicious site asks the user’s browser to carry out an action on a site that the user has an active session on and the victim site carries out that action believing that the user intended that action to occur. In other words the problem arises when a web application relies purely on session cookies to authenticate requests.
Let’s look at a simple example. I want my site to add me to your 37Signals Highrise contact list.
37 Signals Highrise
Working out how to do this is pretty simple. I go to Highrise and take a look at the “Add a person” form in Firebug or using View Source. It’s a fairly straightforward form that submits to http://ianloic.highrisehq.com/people and has a bunch of fields. I can easily recreate that form on my own server. Since Highrise uses a different domain for each user I ask the user to enter their domain name. I use the same username I use everywhere else so it should be pretty easy to fool my user into giving me their Highrise domain name. Most other sites do not have unique per-user URLs.
The page is here and the source is here. Provided you’re logged in and you enter your highrise domain correctly clicking “Add me” will add my name to your contacts list because 37Signals’ servers have no idea that the request is not coming from their application.
It’s fairly straight-forward to modify a form like this to automatically submit on page load. It’s pretty easy to put it in an hidden IFRAME so the user doesn’t even know what’s going on.
By using a unique per-user URL the guys at 37 signals have made this exploit non-trivial. This is not the default behavior from the Ruby on Rails framework. By default action URLs are very predictable. For example if we take a look at the social bookmarking site Magnolia we see predictable URLs all over the place.
Magnolia
As soon as you log in to Magnolia you are presented with a form for adding a bookmark by typing in its URL. This is a fantastic user experience. Unfortunately for Magnolia’s users anyone that submits this form on their behalf can add bookmarks and the form’s action (http://ma.gnolia.com/bookmarks/quicksave) is the same for all users. A trivial page that adds a bookmark to a visitor’s Magnolia account would look like this (try it):
<html>
<head><title>Ma.gnolia</title></head>
<body onload="document.getElementById('f').submit()">
<form id="f" method="post"
action="http://ma.gnolia.com/bookmarks/quicksave">
<input type="text" value="http://ian.mckellar.org/"
name="url" id="url" type="hidden"></input>
</form>
</body>
</html>
But it gets even worse. Since by default Rails allows GET as well as POST submissions you can call an action from an IMG tag (try it):
<img src="http://ma.gnolia.com/bookmarks/quicksave?url=http://ian.mckellar.org/">
Magnolia is not unique in the behavior. Unless a site’s developer has gone out of their way to prevent it, this class of attacks will affect every Rails site. Most of the popular sites I’ve looked at exhibit some vulnerabilities.
Other Rails sites I’ve looked at attempt to do their input validation in JavaScript rather than in Ruby which leaves them open to JavaScript injection and hence XSS attacks. This is a far more serious attack that I can cover separately if there is interest.
Solutions
Easy Solutions
There aren’t any good easy solutions to this. A first step is to do referrer checking on every request and block GET requests in form actions. Simply checking the domain on the referrer may not be enough security if there’s a chance that HTML could be posted somewhere in the domain by an attacker the application would be vulnerable again.
Better Solutions
Ideally we want a shared secret between the HTML that contains the form and the rails code in the action. We don’t want this to be accessible to third parties so serving as JavaScript isn’t an option. The way other platforms like Drupal achieve this is by inserting a hidden form field into every form that’s generated that contains a secret token, either unique to the current user’s current session or (for the more paranoid) also unique to the action. The action then has to check that the hidden token is correct before allowing processing to continue.
This is a pain in the arse to write by hand.
What is really required at the Rails level is a of form API that can generate and consume forms securely. In Drupal all form HTML is generated and parsed by the framework. This allows application developers to protect themselves from XSRF without even knowing they are.
There’s a plugin called Secure Action but I’m not sure how well it works. The dependence on a static shared salt rather than a randomly generated secret in the user’s session concerns me. The way it puts the signature in the URL makes me nervous too. It’s better than nothing though.









29 Comments
Cool hack but it doesn’t seem very hard to fix secure-action-plugin, from a cursory glance, looks like it fixes most of the problem (assuming it does what it purports to do).
The only additional fix would be to use two pieces of data: one with the current time and the other holding an encrypted version of that time (encrypted with the secret salt). The server creates fresh user-specific values for every form and then, on form submission, validates that they match.
As to the signature on the URL, it seems like you could replace it with a form field, but at the cost of adding a line to all your forms (but it could be the *same* line for all of them, something like “< %= secure_action_field %>“).
JB
P.S. - Hey! Who added this link to my Magnolia account?
Ha, nice digg-bait title. Ha, nice digg-bait title. “10 reasons why drupal is more secure than ruby on rails” might be a better one. Though, calling insecurity a ‘best practice’ is a pretty lame stretch.
Security is a concern for any framework. The fact that Drupal provides csrf protection by default is great. I wonder how many other frameworks do the same? I wrote a rails plugin (http://svn.techno-weenie.net/projects/plugins/csrf_killer/README) that lets you specify user-specific shared secret hashes for the session, and automatically adds it to all ajax links and forms. It works well, but mainly stops a bunch of spam for me. I’ll probably push for including it in Rails 2.0.
Also, rails has had a simple way to restrict actions to certain request methods for awhile now. The problem is, few (Like ma.gnolia) bothered to protect all their actions properly. However, Rails 1.2 shipped with new REST support which is becoming the default best practice for writing your apps. You’ll notice there are no unsafe GET requests now. For instance, posting to http://ianloic.highrisehq.com/people invokes the ‘create’ action, but GET requests invoke the ‘index’ action.
Closing Holes Thanks for the nudge Ian, we have now fixed the problem and are properly checking for referrers.
I am a little surprised to just run across this blog post, however. Fortunately, we’ve never seen this used in the wild, But, I think it would have been courteous to let us know about this exploit before publishing it. We’re very accessible and and I think we’ve made our commitment to respond to our members’ concerns quite clear. We really would have like to have been given the opportunity to close it first.
I’d also like to say that we take full responsibility for the existence of this hole, and it has absolutely nothing to do with Rails. Our choice of development platform is completely irrelevant.
Additionally, completely aside from Rails, we believe that urls are part of our user interface, and should be clear, easily rememberable and guess-able. We don’t believe that Security through Obscurity is a good practice in general, and in this arena in particular.
Re: Closing Holes @Larry, I’m sorry I didn’t come to you guys with this. I didn’t mean this as an attack on your site in particular, I just glanced around for a Rails site that had predictable URLs and you were one of the first sites I looked at.
As for URLs being part of your UI or API, I understand that instinct, but I think its dangerous. I think that having URLs that aren’t predictable to an external site isn’t security through obscurity, its establishing a shared secret between browsers viewing your site and your servers. It you gave everyone the same cryptic URL, that would be security through obscurity.
As rick mentioned above, As rick mentioned above, there’s a simple plugin you can include to instantly secure your application against basically all CSRF attacks, odds are it’ll be part of rails 2.0, but until then it’s a one liner to install. If you’re using the 1.2 REST methods your application is automatically safe for GETs, and that’s ‘ruby on rails best practice’.
I’m not sure what your intentions were when posting this, but the fact you posted this without mentioning the potential security problems to the sites in question raises some questions about your motives. The inflammatory title, and your ignoring rick’s comments makes it seem worse. All the best anyways, and here’s hoping CSRF becomes as easy to fix in all those other web frameworks
Interesting article marred by the obvious anti-rails bias By using the title “Insecurity is Ruby on Rails Best Practice” you seem to be indicating that the best thing about Rails is the insecurity of it, therefore everything else must be worse.
I aggree with Michael Koziarski’s questioning of your motives. Was this a mistake, an attempt to get on digg or do you just not like rails (do you seriously believe that the best feature of rails is it’s insecurity)? I notice this site uses Drupal so you obviously have a preference, but this wasn’t declared and as this article seems to be trying provide an objective look at CSRF attacks/security I think it would have been wise.
The content was useful, thanks, but you would have got a lot more respect from me for it if you were more up-front about your bias and motives.
I suppose this is a rehash I suppose this is a rehash of what others have already posted, but:
Rails, like any other system, makes difficult decisions about how to balance power and flexibility with ease of use. It seems disingenuous to single out Rails, as I’m sure it’s not hard to make insecure applications in a wide variety of other frameworks and languages. If you had posted examples for Rails, something in Java, something in PHP, and concluded that we have a long way to go in creating web applications, I think the responses would be much less defensive than they are.
Checking the HTTP Referrer Checking the HTTP Referrer is not a good idea. Firstly, there are valid reasons why the user could have the Referrer header feature turned off in their web browser. Secondly, an attacker can forge an HTTP Referrer header, although (probably) not via a CSRF.
general problem in the web as michael and rick mentioned above, the whole REST approach in rails 1.2 solves the GET/POST unclarity problem.
and the other thing you’re mentioning, about submitting forms from an other site, has nothing to do with rails. this is a general problem in the web and i think you’re suggestion with the secret token would be a way in the wrong direction (because it’s not RESTful). in my opinion this should be the job of the browser. the user should get a warning popup, or whatever, for every request he submits (except GETs) pointing to a different host than the current one.
REFERERS Checking the HTTP_REFERER doesn’t really matter because you can send a request via nc or curl and modify the http headers as well.
not CSRF While I understand where you’re coming from with “‘referer’ isn’t secure”, the point of the CSRF attack is that the browser sends the session cookie back to the target site.
In the trust relationship that exists between user < => browser < => cookie site, the browser is responsible for sending proper cookies and Referer headers. An exploit site would need to get the browser to send a spoofed Referer header along with the cross-site request. If the cookie site trusts the browser to do the right thing with the cookie, they can safely trust it to do the right thing with Referer as well.
nothing like repeating again nothing like repeating again and again but it is googd for you tu understand: what on earth that has to do specific to rails?
Nothing motivates like fear… W.r.t. good programming practices: you expose an important issue: make forms secure and
validate the source of POST’ed entries.
You expose a poor programming practice.
Do a lot of web forms have this exposure, in your survey of the web?
The Rails folks come out at night. Hey Ian, looks like you got the Rails folks up in arms. Pretty easy, really.
Oh, and *wave*.
Trolling is any blogger’s best practice You are guilty of trolling the Rails community, but I do appreciate your exposing this important issue! Of course, I am guilty of feeding into your successful trolling by responding…
I am not sure if building more restrictions into Rails and other app frameworks will solve the issue of common developer carelessness - with each security-related convenience, dangers such as performing state-changing operations with GETs will seem that much farther away and that much more likely to be imappropriately placed into a seemingly innoculous method such as “show”. However, security through convenience can be effectively encourage through “easier means to do the right thing” - such as how ActiveRecord enables the programmer to prevent sql injection attacks.
In developing my first Rails app, I put logic in the controller methods to prevent state-changing GETs. I am somewhat negative about the new REST approach in 1.2 on first blush. This could be made a lot easier with a standard one-line before_filter to identify methods that can only process a POST request.
Lastly, Luke’s suggestion is interesting and should be closely considered by browser developers: “in my opinion this should be the job of the browser. the user should get a warning popup, or whatever, for every request he submits (except GETs) pointing to a different host than the current one.”
- Jamie
Well, being digg-bait wasn’t Well, being digg-bait wasn’t really the point, otherwise I would have made it “10 something somethings”. But the title was intended to be a bit provocative.
What I mean by it is that the practices that are used by the flagship rails sites and the techniques that are taught in the Rails tutorials and books are insecure. And that insecurity isn’t understood well by the community.
I don’t have any motives I don’t have any motives beyond raising some concerning issues. I realized this was a problem when I started digging around a major rails site some friends were developing. I use Drupal as a counterexample of a framework that does protect against this kind of attack because I was looking through the forms it was generating and was pleasantly surprised to discover it takes care of this problem automatically.
And I don’t think the best feature of rails is its insecurity, nor do I think I suggested that. I suggested that the best-practice - the techniques that are used by the most prominent rails developers (such as 37signals) and are taught in the most prominent rails tutorials and books are insecure in was that are not obvious to most developers. That’s a real concern to me.
Many many sites depend on Many many sites depend on being able to submit forms to other sites transparently. Many of them depend on cookies being set. Google authentication depends on this. Asking browser manufacturers to warn users about it is ridiculous. It’s up to web applications (and the frameworks they’re built on) to trust the cookies as much as is appropriate and not too much.
It would be easy to make the same argument about SQL injection attacks or Cross Site Scripting too. Thankfully people have implemented frameworks that do a good job of taking care of these problems for us.
Rails doesn’t provide the Rails doesn’t provide the protections that many other frameworks provide while hiding the scary details from developers. That’s bad.
Many forms do, but many Many forms do, but many don’t. Services from major players like Google and Yahoo don’t have this problem. Digg doesn’t have this problem, del.icio.us doesn’t have this problem. Drupal sites don’t have this problem, but just about all the Rails sites I looked at have this problem.
Basically any site that becomes popular enough to have people trying to exploit it will have to fix this problem. It would be better if the framework took care of the ugly details for us.
Is it still a troll if I Is it still a troll if I have a valid point?
The reason I blame Rails and not Rails application developers is that the framework seems to abstract away and hide so many ugly details.
As for Luke’s suggestion of modifying browser behavior, I think that’s ridiculous. Perhaps I’m coming from the perspective of being a six year browser development veteran, but we’ve had the cookies web security model for ten years now. I don’t want to give it up and introduce yet-another security popup just so someone can have their “rest” URLs. Since it’s hard enough to get CSS implemented in popular browsers I wouldn’t hold my breath for this.
Any language This sort of exploits go for all languages.. PHP (especially when using register_globals) is just as bad. But both in PHP and Rails, this is easily preventable. Just don’t expect the language/framework to automagically do all the work for you.
interesting point You’re point is actually very interesting. And I think you’re right, this isn’t really the job of the webapplication but the browser’s one. HTTP supposed to be a stateless protocol and although there are some exceptions to this (sessions/cookies) we shouldn’t treat a webapplication like a normal application. Trying to control the workflow with some mysterious hashes sending over with every request is just not how the web should work. The web supposed to be as simple as possible and everything else should be reconsidered because there’s probably a better solution.
django Django comes with a nice piece of middleware that takes the drupal approach: outgoing forms get an additional hidden field and incoming requests are checked for the token… This works automagically for most normal means of generating forms (ie, if you build forms in js libraries with dom calls or something, there’s no way to intercept the served html form…)
See http://www.djangoproject.com/documentation/csrf/
Nice to know that thanks for sharing.
Look, it’s rails behaving as drupal…. It’s prety easy to add the drupal behavior you so like to Rails. Here’s some real basic code that gets the job done with a new secret key for each controller, action and request. It should get you started. Add the before and after filters to the controllers as needed… you might also want to test for the request type and do other sexy things
after_filter AddSecretKey, :only=>[:edit,:new]
before_filter TestSecretKey, :only=>[:create,:update]
class TestSecretKey
def self.filter(controller)
false if !controller.params.empty? && controller.params["secret"] != controller.session[:secret]
end
end
class AddSecretKey
def self.filter(controller)
secret = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )[0..20]
controller.session[:secret] = secret
controller.response.body.sub! “</form>”, “<input type=\”hidden\” name = \”secret\” value=\”#{secret}\”/></form>”
end
end
The verify method is what you’re asking for… > This could be made a lot easier with a standard one-line before_filter to identify methods that can only process a POST request.
Check the verify class method of ActionController. It does exactly that!
http://api.rubyonrails.com/classes/ActionController/Verification/ClassMethods.html
Insecurity … blah blah blah - said a post on a drupal site Ironic … really.
Insecurity It is obvious that any framework is going to be as insecure as the person behind the keyboard. Drupal (php) is not necessarily insecure, but of course if you don’t know what goes on “behind the scenes” then you become a target.
In my opinion the freshness of the framework provide new security challenges that indeed need to be address before going live with RoR.
Jenn,
Ruby on Rails Professional Developer