Ruby on Rails Security Guide


Ruby on Rails does a decent job in handling security concerns in the background. You will have to configure your application to avoid few security attacks while plugins would be required for many security concerns which are not at all or poorly managed by rails.

In this article I have described the security issues related to a ruby on rails web application. I have followed DRY by linking to articles with good explanation and solutions to security concerns wherever required. This guide can also be used as a quick security check for your current web application.

Table of Contents

  1. Authentication
  2. Model
    1. SQL Injection
    2. Activerecord Validation
    3. Creating records directly from parameters
  3. Controller
    1. Exposing methods
    2. Authorize parameters
    3. Filter sensitive logs
    4. Cross Site Reference(or Request) Forgery (CSRF)
    5. Minimize session attacks
    6. Stop spam on your website from DNS Blacklist
    7. Caching authenticated pages
  4. View
    1. Cross site scripting(XSS) attack
    2. Anti-spam form protection
    3. Hide mailto links
    4. Use password strength evaluators
  5. Miscellaneous
    1. Transmission of Sensitive information
    2. File upload
    3. Secure your setup / environment
    4. Mysql configuration
    5. Use good passwords
  6. Security plugins directory
Tagged as  
Posted on 20 September
85 comment Bookmark   AddThis Social Bookmark Button Updated on 23 February

Advanced acts_as_solr


This article extends our acts_as_solr : search and faceting tutorial and talks about how to manage rails associations, solr indexes and more with acts_as_solr.

Table Of Contents

  1. Rebuild Solr index
  2. Import existing Solr index or your custom Solr schema.xml
  3. Highlight search term
  4. Rails associations and acts_as_solr
  5. Tips

Rebuild Solr Index

rebuild_solr_index is a class method to re-build your model indexes on import of external data.

For large tables rebuilding Solr index is a time consuming process. See the fifth line in the pseudo code below (index optimization call), it makes rebuild_solr_index a slow process. For large tables, you do not want optimization to take place for each object added to the table. Whereas, removing optimization calls slows down the process of updating solr index.

1
2
3
4
5
6
7
8

## pseudo code
def rebuild_solr_index
  for_each_row_in_table do |doc|
    doc.save_to_solr_index
    index.optimize
  end
end

The solution to the problem is to use batch_size in #rebuild_solr_index. With batch size, say for example 100, the index optimization call is executed after indexing 100 rows.

Tagged as   
Posted on 14 September
6 comment Bookmark   AddThis Social Bookmark Button Updated on 23 February

archive

Ruby on Rails Security Guide

Ruby on Rails does a decent job in handling security concerns in the background. You will have to configure your application to avoid few security attacks while plugins would be required for many security concerns which are not at all or poorly managed by rails.

In this article I have described the security issues related to a ruby on rails web application. I have followed DRY by linking to articles with good explanation and solutions to security concerns wherever required. This guide can also be used as a quick security check for your current web application.

Table of Contents

  1. Authentication
  2. Model
    1. SQL Injection
    2. Activerecord Validation
    3. Creating records directly from parameters
  3. Controller
    1. Exposing methods
    2. Authorize parameters
    3. Filter sensitive logs
    4. Cross Site Reference(or Request) Forgery (CSRF)
    5. Minimize session attacks
    6. Stop spam on your website from DNS Blacklist
    7. Caching authenticated pages
  4. View
    1. Cross site scripting(XSS) attack
    2. Anti-spam form protection
    3. Hide mailto links
    4. Use password strength evaluators
  5. Miscellaneous
    1. Transmission of Sensitive information
    2. File upload
    3. Secure your setup / environment
    4. Mysql configuration
    5. Use good passwords
  6. Security plugins directory

Authentication

Authentication is the foremost requirement of most of the web applications to authenticate and give privileges to their users. Apart from normal authentication mechanism rails have plugins for OpenID, CAS and Access Control. Build your own authentication system only if your requirements are very unique or you do not trust other implementations.

Plugin - Restful Authentication (recommended) - easy to use and you can tweak it according to your requirements. Build your own authentication. You should rarely need to do this ... Restful Authentication is quite flexible. OpenID - a universal authentication system to avoid use of multiple username and password on the Internet. OpenID is getting quite famous now-a-days. Access Control : To easily proivde different priviliges to your users. There are a lot of cool plugins available for access control. Centralized Authentication Server - is used to implement single login/password for your users across multiple application. It can also be used for a single sign-on system. For example, Gmail and Google Reader have a single sign-on between them. Use Google Authentication API to let your users login using their google username and password. More Plugins :

- Model -

SQL Injection

The problem arises when metacharacters are injected into your queries to database. Rails has a very good support to avoid SQL injection if you follow conventions in issuing queries to your database.

Description : Alternate Solution - use hash for specifying conditions in #find

Activerecord Validation

To validate the contents of model object before records are created/modified in the database. Activerecord validations are very useful over database data-type constraints to ensure values entered into the database follow your rules. You might have javascript validations for forms but javascript can easily be switched off. Use javascript validations only for better user experience.

Description : Conditional validation using :on and :if options. Checkout this cool video Be careful using validates_uniqueness_of, it has problems when used with :scope option. Open bug tickets : Use :allow_blank to pass validations if value is nil or empty string Testing Validations - do read the comments in this article Useful Tips
  • Its easy to manage 'nil' values using :allow_nil, its quite handy. For ex: set :allow_nil => true in validates_uniqueness_of to check uniqueness of non-nil values and ignore nil values
  • validates_presence_of is not required if you are using validates_format_of, unless regular expression accepts empty string.

Creating records directly from parameters

While creating database records directly from form params, a malicious user can add extra fields into the params and manually submit the web page which will set values of fields which you do not want user to set.

Description : Alternate Solution - Trim the parameters to keep the required keys and remove the others.

- Controller -

Exposing methods

Use private and protected in controller for methods which should not be actions. Actions are pubic methods and can be invoked from the browser.

hide_action : If non-action controller methods must be public, hide them using hide_action. Be careful of bypassing private and protected using meta-programming

Authorize parameters

Always authorize user request. By tweaking form parameters or url a user can send request to view/modify other users information if there is no proper authorization of parameters.

For example :
1
2
3
4
5
6
7
8

## To find information of an order which belongs to a particular user.

#Incorrect :
@order = Order.find(order_id)

#Correct :
@order = @user.orders.find(order_id)
Do not ignore hidden fields - a user can easily modify their value, so suspect them similar to params[:id]

Filter sensitive logs

Prevent logs of sensitive unencrypted data using #filter_parameter_logging in controller. The default behavior is to log request parameters in production as well as development environment, and you would not like logging of password, credit card number, etc.

Video Tutorial

Cross Site Reference(or Request) Forgery (CSRF)

In a CSRF attack, the attacker makes victim click on a link of his choice which would contain a GET/POST request and causes web application to take malicious action. The link could be embedded in a iframe or an img tag. Its recommended to use secret token while communicating with user to avoid this attack.

Its little complex to understand this attack. So, only those readers who are very enthusiastic to know about it, please read the Description below. Rest can directly move ahead to use the plugin.

Description : Use Get and Post appropiately (note : Both get and post are vulnerable to CSRF) Example - Gmail CSRF security flaw Plugin - CSRF Killer (recommended) - it requires edge rails

Minimize session attacks

If an attacker has session-id of your user, he can create HTTP requests to access user account. An attacker can get session-id by direct access to user machine or is able to successfully run malicious scripts at user machine. In this section we will talk about how to avoid or minimize the risk if attacker has user session-id. Following steps are helpful:

  1. Store IP Address, but creates problem if user moves from one network to another.
  2. Create a new session everytime someone logs in.
  3. Expire session on user logout, user is idle for a time period or on closing of browser/tab. For maximum security expire sessions on all the three conditions.

Code for session expiry on timeout
1
2
3
4
5
6
7
8
9
10
11
12

## Timeout after inactivity of one hour.
MAX_SESSION_PERIOD = 3600

before_filter :session_expiry

def session_expiry
   reset_session if session[:expiry_time] and session[:expiry_time] < Time.now

   session[:expiry_time] = MAX_SESSION_PERIOD.seconds.from_now
   return true
end
Plugin - Session Expiration for session expiry on timeout Do not put expiry time in the cookie unless your cookie information is properly encrypted. If not, use server side session expiry. Persistent session / login in rails - global setting in enviornment.rb
1
2

ActionController::Base.session_options[:session_expires] = <i>say after two years</i>
Persistent session / login in rails - to give your users a feature - remember me

Stop spam on your website from DNS Blacklist

Avoid access to your website from IP addresses which are present in DNS Blacklist(DNSBL).

Plugin - DNSBL check

Caching authenticated pages

Page caching does bypass any security filters in your application. So avoid caching authenticated pages and use action or fragment caching instead.


- View -

Cross site scripting(XSS) attack

Cross Site Scripting is a technique found in web applications which allow code injection by malicious web users into the web pages viewed by other users. An attacker can steal login of your user by stealing his cookie. The most common method of attack is to place javascript code on a website that can receive the session cookie. To avoid the attack, escape HTML meta characters which will avoid execution of malicious Javascript code. Ruby on Rails has inbuilt methods like escape_html() (h()), url_encode(), sanatize(), etc to escape HTML meta characters.

Description Can we avoid tedious use of h() in views? Sanitize() is used to escape script tags and other malicious content other than html tags. Avoid using it ... its unsecure. Use white_list instead. White_list plugin

Anti-spam form protection

Use Captcha or Javascript based form protection techniques to ensure only human can submit forms successfully.

When using Captcha do ensure the following :

  1. Images are rendered on webpage using send_data and are not stored at the server, because its not required to store images and are redundant.
  2. Avoid using algorithm used by standard Catpcha plugins as they can easily be hacked, instead tweak an existing algorithm or write your own.
  3. Use a Captcha which does not store secret code or images in filesystem, as you will have trouble using Captcha with multiple servers.

Tutorial - a nice article on concepts of captcha Plugin - ReCaptcha (recommended) Plugin - BrainBuster - a logic captcha based on simple puzzles, math and word problems. By default, it has limited set of problems and you would have to come up with large set of your own problems. Plugin - Simple Captcha (not recommended) as it breaks all the must have features of a good Captcha implementation. For less critical systems like blogs, a more user-friendly option can be use of CSS based technique or JavaScript based plugin unlike Captcha. Both JavaScript and CSS based techniques can only avoid spam from dumb or general bots. If an hacker specifically targets your site or bot is smart enough, you are dead, so be careful. Captcha with Multiple Servers

Hide mailto links

Mailto links in a webpage can be attacked by e-mail harvesting bots. Use the plugin CipherMail to generate a 1024 bit random key and obfuscate the mailto link.

Plugin - CipherMail

Use password strength evaluators

A lot of people have used password strength evaluators simply because its used by google in their registration form. You can use it to help your users register with strong password. But I don't think its a must have security addon. Uptill now I have not found a good algorithm to assess strength of a password, but some of them are reasonable.

Also, if there is an open source tool or algorithm for evaluating password strength, it can easily be broken. So, you might consider tweaking the algorithm or building one from scratch.

Tools

- Miscellaneous -

Transmission of Sensitive information

Use SSL to encrypt sensitive data between transfer from client to server. SSL hits server performace, so you might consider using SSL only for few pages which transfer sensitive data to and fro.

Plugin ssl_requirement Mongrel, rails, apache and SSL Controller in SSL subdomain Sample SSL code in rails

File upload

Be very careful when you allow your users to upload files and make them available for other users to download.

Description Must read - Section 26.7 of Agile web development with rails - 2nd edition In place file upload 3 plugins for file upload reviewed at :

Secure your setup / environment

Proper Mysql configuration

Use good passwords


Security plugins directory



Note : I will keep this security guide updated. Any additions/improvements are welcome.

Advanced acts_as_solr

This article extends our acts_as_solr : search and faceting tutorial and talks about how to manage rails associations, solr indexes and more with acts_as_solr.

Table Of Contents

  1. Rebuild Solr index
  2. Import existing Solr index or your custom Solr schema.xml
  3. Highlight search term
  4. Rails associations and acts_as_solr
  5. Tips

Rebuild Solr Index

rebuild_solr_index is a class method to re-build your model indexes on import of external data.

For large tables rebuilding Solr index might be time consuming process because each row is comitted to solr index. Rather saving each row individually, you can save them in batch. Using batch_size argument, in #rebuild_solr_index(say for example 100), solr index would be updated with 100 rows in 1 commit rather updating solr index for each of them.

If you do not want to index the complete data but only few rows based on conditions, use optional arguments batch_size and finder in rebuild_solr_index.

  • batch_size : to send this many rows to solr for index update. Default value is 1.
  • finder : it is used for conditional indexation. It takes a method as argument which returns objects to be indexed. Default method is :
    1
    2
    3
    4
    
    
    def finder(ar, options)
      ar.find (:all, options.merge({:order => self.primary_key}))
    end
    

Import existing Solr index or your custom Solr schema.xml

Assumption: Column names in table definition are same as the names of the fields using which index has been/will be created.

  • Add your indexed fields to vendor/plugins/acts_as_solr/solr/solr/conf/schema.xml (continuing with example of our previous tutorial, we add name1, brand, resolution to schema.xml).
    Remember, to copy them to text field using solr's copyfield directive. text is the default field/column in which search is made, so using copyField we add all data to default search field. The new schema.xml looks like...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    
    <field name="text" type="text" indexed="true" stored="true" multiValued="true"/> 
    <field name="name1" type="text" indexed="true" stored="false" />
    <field name="brand" type="string" indexed="true" stored="false" />
    <field name="resolution" type="sfloat" indexed="true" stored="false" />
    
    <copyField source="*_facet" dest="text"/>
    <copyField source="brand" dest="text"/>
    <copyField source="name1" dest="text"/>
    
  • Apply our patch (download). For indexing and searching, acts_as_solr suffixes dataType (e.g. int, float, string etc) of column to the column name, this patch tells acts_as_solr not to do this since we have explicitly created entries for these columns in schema file:
    1
    2
    3
    
    
    cd /path/to/rails/dir
    patch -p0 custom-schema.patch
    
  • Modify your acts_as_solr definition:
    1
    2
    3
    4
    
    
    class Camera
      acts_as_solr :custom_schema=>true, :fields=>[:name,:brand,:resolution]
    end
    
  • ... and it works!

Highlight search terms

Goal is to highlight search term(s) in search results. Follow the steps below :

  • Modify solrConfig.xml to enable highlighting. Apply our patch (download) as:
    1
    2
    3
    
    
    cd /path/to/rails/dir
    patch -p0 highlight-solrconfig.patch
    
  • acts_as_solr does not stores any of the field values as it is in its index. We need to modify the configuration file vendor/plugins/acts_as_solr/solr/solr/conf/schema.xml to enable storage of data, which is required to return the highlighted text data. For example, we modify configuration file to store the fields having text dataType to get highlighted matching terms for name1 field.
    1
    2
    3
    4
    5
    
    
    <!-- Original config -->
    <dynamicField name="*_t" type="text" indexed="true" stored="false"/>
    <!-- New config -->
    <dynamicField name="*_t" type="text" indexed="true" stored="true"/>
    
  • Apply one more patch in the same way as the previous one. This patch enables acts_as_solr to handle the option of highlighting in find_by_solr queries and parses the results returned by solr. And now we are ready to roar...

  • Lets see it in action with an example
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    
    @results = Camera.find_by_solr("canon powershot", :highlight=>{:fields=>"name1"})
    @results.highlights
    ## returns hash of objects id and hash of the columns with matched text data
    {1=>{:name1=>"<em>Canon</em> <em>Powershot</em> sd1000"} ... }
    
    #You want to display names with highlighted terms:
    @results.docs.each do |doc|
       <%= @results.highlights[doc.id][:name1]  -%><br/>
    end
    
  • Options for highlighting (You can also refer them here)
    1. fields: columns to highlight terms for (default is none, and no highlighted text)
    2. prefix: You do not want to have "<em>" tag around matched search term(s), tell your tag here. (eg. "<span class='highlight'>")
    3. suffix: Ending tag for matched term(s)
    4. max_snippets: stop searching the column after max_snippets matched terms found
    Currenlty acts_as_solr does not implements these options at each column level, so you cannot ask to apply some of the options to only one (or few) columns. For e.g. you cannot ask acts_as_solr to use <em> tags for one field and <span class='highlight'> for other field.


Note: If you want both the functionalities Custom schema and Highlighting together, use these two patches. Patch 1 and Patch 2.

Rails associations and acts_as_solr

Lets say I have two models, User (columns: username, password) and UserProfile (columns: full name, location, favorite food .... and many more). These two models have one-to-one association (:has_one, :belongs_to) between them and I have two problems :

  1. When searching the User model, I also want to search UserProfile model.
  2. I want to allow faceting based on few columns in UserProfile lets say location and favorite food.

Fabio Confalonieri has written a small hack for the solution to both the problems along with good explanation.

Tips:

  1. Wanna see the statistics for created index?
    Download
    this xsl file as luke.xsl to vendor/plugins/acts_as_solr/solr/solr/conf/xslt/ and then open url: http://localhost:8982/solr/admin/luke?wt=xslt&tr=luke.xsl

  2. If your search results are not good enough because the text is not indexed properly, it might be due to presence of acronyms or words having apostrophes or html content in your data. Solr provides you with many options for analyzing and modifying text before it is indexed and searched. Read these options at Solr's Wiki page. Remember, you need to make these modifications in schema.xml file.

  3. Note : The default behaviour of acts_as_solr is to index model objects automatically upon save or update of a record.