This site is the archived OWASP Foundation Wiki and is no longer accepting Account Requests.
To view the new OWASP Foundation website, please visit https://owasp.org

Difference between revisions of "Ruby on Rails Cheatsheet"

From OWASP
Jump to: navigation, search
(Security-related headers)
Line 213: Line 213:
 
== Security-related headers ==
 
== Security-related headers ==
  
To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter.
+
To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter).
  
 
   response.headers['X-header-name'] = 'value'
 
   response.headers['X-header-name'] = 'value'

Revision as of 17:55, 14 February 2013

DRAFT CHEAT SHEET - WORK IN PROGRESS

Introduction

This article intends to provide quick basic Ruby on Rails security tips for developers. The Rails framework abstracts developers from quite a bit of tedious work and provides the means to accomplish complex tasks quickly and with ease. New developers, those unfamiliar with the inner-workings of Rails, likely need a basic set of guidelines to secure fundamental aspects of their application. The intended purpose of this doc is to be that guide.

Items

Command Injection

Ruby offers a function called “eval” which will dynamically build new Ruby code based on Strings. It also has a number of ways to call system commands.

  eval("ruby code here")
  System("os command here")
  `ls -al /`   (backticks contain os command)
  Kernel.exec("os command here")

While the power of these commands is quite useful, extreme care should be taken when using them in a Rails based application. Usually, its just a bad idea. If need be, a whitelist of possible values should be used and any input should be validated as thoroughly as possible.

SQL Injection

Ruby on Rails is often used with an ORM called ActiveRecord, though it is flexible and can be used with other data sources. Typically very simple Rails applications use methods on the Rails models to query data. Many use cases protect for SQL Injection out of the box. However, it is possible to write code that allows for SQL Injection.

Here is an example (Rails 2.X style):

   @projects = Project.find(:all, :conditions => “name like #{params[:name]}”)

A Rails 3.X example:

   name = params[:name]
   @projects = Project.where(“name like ‘“ + name + “‘“);

In both of these cases, the statement is injectable because the name parameter is not escaped.

Here is the idiom for building this kind of statement:

   @projects = Project.find(:all, :conditions => [ “name like ?”, “#{params[:name]}”] )

An AREL based solution:

   @projects = Project.where("name like ?", "%#{params[:name]}%")

Use caution not to build SQL statements based on user controlled input. A list of more realistic and detailed examples is here: rails-sqli.org.

Cross-site Scripting (XSS)

By default, in Rails 3.0 protection against XSS comes as the default behavior. When string data is shown in views, it is escaped prior to being sent back to the browser. This goes a long way, but there are common cases where developers bypass this protection - for example to enable rich text editing. In the event that you want to pass variables to the front end with tags intact, it is tempting to do the following in your .erb file (ruby markup).

   <%= raw @product.name %>   
   <%= @product.name.html_safe %>       These are examples of how NOT to do it!
   <%= content_tag @product.name %>

Unfortunately, any field that uses raw like this will be a potential XSS target. Note that there are also widespread misunderstandings about html_safe. This writeup describes the underlying SafeBuffer mechanism in detail. Other tags that change the way strings are prepared for output can introduce similar issues, including content_tag.

One way to manage cases like this is to use a Rails provided helper method called sanitize:

   <%= sanitize @project.name, :tags => %w(h1 h2 h3 h4 h5), :attributes => %() %>

This eliminates tags other than <h1>, <h2>, <h3>, <h4>, <h5>. It also disallows attributes on those tags. Note that it is critical to understand the tags you enable here. <img> may seem like an innocuous tag, but through various attributes on <img/> that are scriptable such as onError=””. Only by sanitizing only innocuous tags can XSS be prevented.

A more attractive alternative to using sanitize and real HTML content is to use an alternative markup language for rich text in an application (Examples include: markdown and textile) and disallow HTML tags. This ensures that the input accepted doesn’t include HTML content that could be malicious.

An often overlooked XSS attack vector is the href value of a link:

   <%= link_to “Personal Website”, @user.website %>

If @user.website contains a link that starts with “javascript:”, the content will execute when a user clicks the generated link:

   <a href=”javascript:alert(‘Haxored’)”>Personal Website</a>

Sessions

By default, Ruby on Rails uses a Cookie based session store. What that means is that unless you change something, the session will not expire on the server. That means that some default applications may be vulnerable to replay attacks. It also means that sensitive information should never be put in the session.

The best practice is to use a database based session, which thankfully is very easy with Rails:

   Project::Application.config.session_store :active_record_store

Authentication

Generally speaking, Rails does not provide authentication by itself. However, most developers using Rails leverage libraries such as Devise or AuthLogic to provide authentication. To enable authentication with Devise, one simply has to put the following in a controller:

   class ProjectController < ApplicationController
       before_filter :authenticate_user

As with other methods, this supports exceptions. Note that by default Devise only requires 6 characters for a password. The minimum can be changed in: /config/initializers/devise.rb

   config.password_length = 8..128

There are several possible ways to enforce complexity. One is to put a Validator in the user model.

   validate :password_complexity
   def password_complexity
      if password.present? and not password.match(/\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+\z/)
          errors.add :password, "must include at least one lowercase letter, one uppercase letter, and one digit"
      end
   end

Insecure Direct Object Reference or Forceful Browsing

By default, Ruby on Rails apps use a RESTful uri structure. That means that paths are often intuitive and guessable. To protect against a user trying to access or modify data that belongs to another user, it is important to specifically control actions. Out of the gate on a vanilla Rails application, there is no such built in protection. It is possible to do this by hand at the controller level.

It is also possible, and probably recommended, to consider resource-based access control libraries such as cancan to do this. This ensures that all operations on a database object are authorized by the business logic of the application.

CSRF (Cross Site Request Forgery)

Ruby on Rails has specific, built in support for CSRF tokens. To enable it, or ensure that it is enabled, find the base ApplicationController and look for a directive such as the following:

   class ApplicationController < ActionController::Base
       protect_from_forgery

Note that the syntax for this type of control includes a way to add exceptions. Exceptions may be useful for API’s or other reasons - but should be reviewed and consciously included. In the example below, the Rails ProjectController will not provide CSRF protection for the show method.

  class ProjectController < ApplicationController
      protect_from_forgery :except => :show

Also note that by default Rails does not provide CSRF protection for any HTTP GET request.

Mass Assignment and Strong Parameters

Although the major issue with Mass Assignment has been fixed by default in base Rails specifically when generating new projects, it still applies to older and upgraded projects so it is important to understand the issue and to ensure that only attributes that are intended to be modifiable are exposed.

When working with a model, the attributes on the model will not be accessible to forms being posted unless a programmer explicitly indicates that:

   class Project < ActiveRecord::Base
       attr_accessible :name, :admin
   end

With the admin attribute accessible based on the example above, the following could work:

   curl -d “project[name]=triage&project[admin]=1” host:port/projects

Review accessible attributes to ensure that they should be accessible. If you are working in Rails < 3.2.3 you should ensure that your attributes are whitelisted with the following:

   config.active_record.whitelist_attributes = true

In Rails 4.0 strong parameters will be the recommended approach for handling attribute visibility. It is also possible to use the strong_parameters gem with Rails 3.x, and the strong_parameters_rails2 gem for Rails 2.3.x applications.

Redirects and Forwards

Web applications often require the ability to dynamically redirect users based on client-supplied data. To clarify, dynamic redirection usually entails the client including a URL in a parameter within a request to the application. Once received by the application, the user is redirected to the URL specified in the request. For example:

http://www.example.com/redirect?url=http://www.example_commerce_site.com/checkout

The above request would redirect the user to http://www.example.com/checkout. The security concern associated with this functionality is leveraging an organization’s trusted brand to phish users and trick them into visiting a malicious site, in our example, “badhacker.com”. Example:

http://www.example.com/redirect?url=http://badhacker.com

The obvious fix for this type of vulnerability is to restrict to specific Top-Level Domains (TLDs), statically define specific sites, or map a key to it’s value. Example:

   ACCEPTABLE_URLS = {
       ‘our_app_1’ => “https://www.example_commerce_site.com/checkout”,
       ‘our_app_2’ => “https://www.example_user_site.com/change_settings”
   }

http://www.example.com/redirect?url=our_app_1

  def redirect
      url = ACCEPTABLE_URLS[“#{params[:url]}”]
      redirect_to url if url
  end

If matching user input against a list of approved sites or TLDs against regular expression is a must, it makes sense to leverage a library such as URI.parse() to obtain the host and then take the host value and match it against regular expression patterns. Those regular expressions must, at a minimum, have anchors or there is a greater chance of an attacker bypassing the validation routine.

Example:

   require ‘uri’
   host = URI.parse(“#{params[:url]}”).host
   validation_routine(host) if host
   def validation_routine(host)
       # Validation routine where we use  \A and \z as anchors *not* ^ and $
   end

Dynamic Render Paths

In Rails, controller actions and views can dynamically determine which view or partial to render by calling the “render” method. If user input is used in or for the template name, an attacker could cause the application to render an arbitrary view, such as an administrative page.

Care should be taken when using user input to determine which view to render. If possible, avoid any user input in the name or path to the view.

Cross Origin Resource Sharing

Occasionally, a need arises to share resources with another domain. For example, a file-upload function that sends data via an AJAX request to another domain. In these cases, the same-origin rules followed by web browsers must be bent. Modern browsers, in compliance with HTML5 standards, will allow this to occur but in order to do this; a couple precautions must be taken.

When using a nonstandard HTTP construct, such as an atypical Content-Type header, for example, the following applies:

The receiving site should whitelist only those domains allowed to make such requests as well as set the Access-Control-Allow-Origin header in both the response to the OPTIONS request and POST request. This is because the OPTIONS request is sent first, in order to determine if the remote or receiving site allows the requesting domain. Next, a second request, a POST request, is sent. Once again, the header must be set in order for the transaction to be shown as successful.

When standard HTTP constructs are used:

The request is sent and the browser, upon receiving a response, inspects the response headers in order to determine if the response can and should be processed.

Note: Do NOT do use the wildcard in the access control header as it allows communication with any site.

   Access-Control-Allow-Origin: *       (Bad example)

Whitelist in Rails:

Gemfile

   gem 'rack-cors', :require => 'rack/cors'

config/application.rb

   module Sample
       class Application < Rails::Application
           config.middleware.use Rack::Cors do
               allow do
                   origins 'someserver.example.com'
                   resource %r{/users/\d+.json},
                       :headers => ['Origin', 'Accept', 'Content-Type'],
                       :methods => [:post, :get]
               end
           end
       end
   end

Security-related headers

To set a header value, simply access the response.headers object as a hash inside your controller (often in a before/after_filter).

 response.headers['X-header-name'] = 'value'

Rails 4 provides the "default_headers" functionality that will automatically apply the values supplied. This works for most headers in almost all cases. Note: this does not account for content security policy.

 ActionDispatch::Response.default_headers = {	  	
   'X-Frame-Options' => 'DENY', 	
   'X-Content-Type-Options' => 'nosniff',	  	
   'X-XSS-Protection' => '1;'
 }

Strict transport security is a special case, it is set in an environment file (e.g. production.rb)

 config.force_ssl = true

For those not on the edge, there is a library (secure_headers) for the same behavior with content security policy abstraction provided. It will automatically apply logic based on the user agent to produce a concise set of headers.

Business Logic Bugs

Any application in any technology can contain business logic errors that result in security bugs. Business logic bugs are difficult to impossible to detect using automated tools. The best ways to prevent business logic security bugs are to do code review, pair program and write unit tests.

Attack Surface

Generally speaking, Rails avoids open redirect and path traversal types of vulnerabilities because of its /config/routes.rb file which dictates what URL’s should be accessible and handled by which controllers. The routes file is a great place to look when thinking about the scope of the attack surface. An example might be as follows:

   match ':controller(/:action(/:id(.:format)))'

In this case, this route allows any public method on any controller to be called as an action. As a developer, you want to make sure that users can only reach the controller methods intended and in the way intended.

Sensitive Files

Many Ruby on Rails apps are open source and hosted on publicly available source code repositories. Whether that is the case or the code is committed to a corporate source control system, there are certain files that should be either excluded or carefully managed.

   /config/database.yml                 -  May contain production credentials.
   /config/initializers/secret_token.rb -  Contains a secret used to hash session cookie.
   /db/seeds.rb                         -  May contain seed data including bootstrap admin user.
   /db/development.sqlite3              -  May contain real data. 


Encryption

Rails uses OS encryption. Generally speaking, it is always a bad idea to write your own encryption.

Devise by default uses bcrypt for password hashing, which is an appropriate solution. Typically, the following config causes the 10 stretches for production: /config/initializers/devise.rb

   config.stretches = Rails.env.test? ? 1 : 10

Updating Rails and Having a Process for Updating Dependencies

In early 2013, a number of critical vulnerabilities were identified in the Rails Framework. Organizations that had fallen behind current versions had more trouble updating and harder decisions along the way, including patching the source code for the framework itself.

An additional concern with Ruby applications in general is that most libraries (gems) are not signed by their authors. It is literally impossible to build a Rails based project with libraries that come from trusted sources. One good practice might be to audit the gems you are using.

In general, it is important to have a process for updating dependencies. An example process might define three mechanisms for triggering an update of response:

  • Every month/quarter dependencies in general are updated.
  • Every week important security vulnerabilities are taken into account and potentially trigger an update.
  • In EXCEPTIONAL conditions, emergency updates may need to be applied.

Tools

Use brakeman, an open source code analysis tool for Rails applications, to identify many potential issues. It will not necessarily produce comprehensive security findings, but it can find easily exposed issues. A great way to see potential issues in Rails is to review the brakeman documentation of warning types.

There are emerging tools that can be used to track security issues in dependency sets, like SourceNinja.


Authors and Primary Editors

Matt Konda - mkonda [at] jemurai.com
Neil Matatall neil [at] matatall.com
Ken Johnson cktricky [at] gmail.com
Justin Collins justin [at] presidentbeef.com
Jon Rose - jrose [at] crowdsecurify.com
Lance Vaughn - lance [at] cabforward.com
Jon Claudius - jonathan.claudius [at] gmail.com
Jim Manico jim [at] owasp.org
Aaron Bedra aaron [at] aaronbedra.com

Related Articles and References

Other Cheatsheets

OWASP Cheat Sheets Project Homepage