1. Technology
Send to a Friend via Email
You can opt-out at any time. Please refer to our privacy policy for contact information.

Dealing With Vulnerabilities: Pay Attention to Idempotence

Countermeasures for Security Issues in Ruby

By

As discussed before, GET requests should be idempotent. Because GET requests are so easy to forge (especially with the IMG tag) and so difficult to protect, a GET request should make no changes to the database whatsoever. GET requests should be used to display information retrieved from the database or forms and submit the information to POST requests, which will make the changes and additions to the database. This goes hand in hand both with the HTTP and REST philosophies, and Rails makes it easy to implement this.

Implement Idempotence with Before Filters

There are two ways of fixing an application made before Rails 2.0 and RESTful designs. The first is to manually add before_filters to check the method of a post. This technique is best used if the current design is not easily converted to a RESTful design.

A simple post_only method must be added to the application controller at app/controllers/application.rb. This method will redirect to the index action if the request type is not POST. The method will then be used to protect non-idempotent actions from GET requests with a global before_filter. Methods that do not require protection will be explicitly excluded from the before_filter with skip_before_filter. This prevents Web developers from adding an action and accidentally forgetting to protect it.

# File: app/controllers/application.rb
class ApplicationController < ActionController::Base
  include AuthenticatedSystem
  before_filter :post_only

  helper :all # include all helpers, all the time

  # See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the <a href="/od/af/g/cookie.htm">cookie</a> session store
  protect_from_forgery # :secret => 'b09ddc8519180659f80e8bb321b7fd26'

  protected
  def post_only
    if not request.post?
      flash[:notice] = "Incorrect request type"
      redirect_to :action => 'index'
    end
  end
end

The following is the first few lines of the Posts controller showing how to exempt some actions from post_only protection.

# File: app/controllers/posts.rb
class PostsController < ApplicationController
  before_filter :login_required, :except => [ :index, :show ]
  skip_before_filter :post_only, :only => [ :index, :new, :edit, :show ]

  # ...snip...
end

Some minor modifications have to be made to the view code. For example, links to the destroy action are common. Since links can only be made to generate GET requests and cannot be protected with the forgery protection, this has to be replaced with a form. Before, the links were generated like this.

<%= link_to 'Destroy', :action => 'destroy', :id => post %>

The links have to be replaced with a form that will use the POST request method. The button_to helper is used instead of link_to. The mini-forms generated by button_to will have the required hidden ID field and use the POST request method.

<%= button_to 'Destroy', { :action => :destroy, :id => post }

Implement Idempotence with RESTful Designs

If at all possible, applications should be designed using RESTful resources. RESTful resources preserve the idempotence rules automatically. However, it's often difficult to mix RESTful resources and non-RESTful controllers. Since the Posts controller has all the methods required to become a RESTful resource, you could try adding the RESTful resource route to the routes.rb file.

ActionController::Routing::Routes.draw do |map|
  map.resources :posts
  map.connect ':controller/:action/:id'
end

Unfortunately, this would be a mistake. While it looks solid, the Posts controller is now accessible as a RESTful resource, while the default routes are still in place for the non-RESTful Account controller. This means the default route also exists for the Posts controller, exposing its non-idempotent actions to GET requests. If, for any reason, you have to mix RESTful resources and non-RESTful controllers, be sure to explicitly list the controllers you wish to have default routes. It's this reason that makes upgrading an application to a RESTful application is often difficult.

ActionController::Routing::Routes.draw do |map|
  map.resources :posts
  map.connect 'account/:action/:id', :controller => 'account'


end

Converting an Existing Site

Converting an existing site to a RESTful site can also be complicated. Not only do you need to convert your controllers to RESTful controllers with the correct action names and semantics, but you also need to change quite a bit in your views. RESTful resources use different URLs from the traditional controllers and have their own helper methods. Since the same protection against CSRF attacks can be achieved manually through careful attention to idempotence and the post_only before filter, it may not be necessary to convert to the RESTful site. Though all new projects generated should be RESTful applications, it's not necessarily required to upgrade old applications to RESTful applications.

  1. About.com
  2. Technology
  3. Ruby
  4. Ruby on Rails
  5. Security
  6. Dealing With Ruby Vulnerabilities: Pay Attention to Idempotence

©2014 About.com. All rights reserved.