Security - The Rails 4 Way (2014)

The Rails 4 Way (2014)

Chapter 15. Security

Ruby on Rails security sucks lolz amirite? No. Well, no to the nuance. Software security does, in general, suck. Virtually every production system has security bugs in it. When you bring pen testers in to audit your app, to a first approximation, your app will lose. While Ruby on Rails cherishes its Cool-Kid-Not-Lame-Enterprise-Consultingware image, software which is absolutely Big Freaking Enterprise consultingware, like say the J2EE framework or Spring, have seen similar vulnerabilities in the past.79

—Patrick McKenzie

Security is a very wide topic, one that we can’t possibly cover in a single book chapter. Still there are things that every competent web developer using Rails should know.

Unlike many other software engineering topics, security is not something that you can solve by investing more hours to fix bugs or inefficient algorithms. Nor it is something you can do by trial and error. You have to know most common attack vectors and how to avoid vulnerabilities.

We will look into common web application security problems and the ways that Rails deals with them, as well as general security guidelines and practices. Along the way we will discuss management of passwords and other private information, log masking, mass-assignment attributes protection, SQL Injection, Cross-Site Scripting (XSS), Cross-Site Request Forgery (XSRF) and more.

15.1 Password Management

One can say leaking your customer’s plain text passwords is probably one of the most embarrassing security problems to have. Especially as the “do not store plain text passwords” mantra is widely known and doing the right thing is really not that hard. Quite easy actually. It usually boils down to using one of the many libraries available. Its also not something that you need to pay constant attention to. You do it once, and you are done.

The biggest problem with storing plain text passwords is that many people use the same password on multiple sites, and so in an event of a leak, you do not only expose user’s accounts in your application, but potentially also put a lot of people other accounts at risk.

The solution is simple and well known: securely hash all passwords. Secure hashing is not the same as encryption, as encryption assumes ability to decrypt and secure hash is a one way function. Once you pass the password through it there is no way to get it back in the original form.

Popular hash functions include MD5 and SHA1. MD5 is considered insecure and is no longer used for password security,80 but you’ll occasionally see it used it to hash values that are not under attack.

“How do you check a hashed password?” you might ask. It’s simple, actually, when we need to test a password given to a login form, we just pass it through the same one way hash function and compare the results.

The actual low level details are a bit more complicated, as we also want to protect against what is known as dictionary rainbow table attack. An attacker might get access to a database of hashed user passwords, and compare the hashes to a table of hashes of dictionary words. Statistically, if you have enough users, a significant amount of them will use dictionary words for their passwords. This will will allow an attacker to find out their password from the rainbow table, and using other information you have stored (like user email) try to gain access to those user’s accounts on other services.

The solution for this problem is using a salt, a random string that is generated for every user during account creation, and which is used together with user’s password when calculating hashed password that we store in the database.

Since the salt is random for every user there is no way to prepare a dictionary table of every dictionary word with every possible salt. So the attacker is left with the brute force attack, actually trying to pick passwords one by one, by trying every possible password combination with user’s salt.

To make it even harder on the attacker, most ‘serious’ password storage libraries use a secure hashing algorithms which was intentionally made very “expensive” to compute, usually by doing a lot of rounds of hash function computation in a sequence.

We’ve delved into the gory details, and you might wonder if it involves a lot of work to implement this stuff in Rails. Actully, all the popular authentication libraries like Authlogic and Devise implement this functionality out of the box. If you don’t want to use a 3rd-party gem, Rails itself has straightforward support for secure password storage with the help of the popular BCrypt library.

To add secure hashed passwords to an ActiveModel class you just need to call the has_secure_password class method.

The usage is very simple:

1 classUser

2 has_secure_password

3 end

From the Rails documentation:

This mechanism requires you to have a password_digest attribute.

Validations for presence of password on create, confirmation of password (using a password_confirmation attribute) are automatically added. If you wish to turn off validations, pass validations: false as an argument. You can add more validations by hand if need be.

If you don’t need the confirmation validation, just don’t set any value to the password_confirmation attribute and the validation will not be triggered.

You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password:

gem 'bcrypt-ruby', '~> 3.0.0'

To actually validate the password during authentication you can use the authenticate method which will be made available on your objects:

User.find_by(email: "").try(:authenticate, params[:password])

The method will return the object itself if the password matches or nil otherwise.

15.2 Log Masking

Great, we are no longer storing the passwords in the database. We are not done though. We might still be leaking the passwords and other sensitive information into the application logs. For every request Rails logs the request parameters into the log file unless parameter name includes one of the “filtered” strings. For a “filtered” parameter Rails will replace the value by [FILTERED] before the logging:

Started POST


for at 2013-02-24 22:29:59 +0000

Processing by UsersController#create as */*

Parameters: {"name"=>"john", "password"=>"[FILTERED]",


Rails protects any parameter that includes password in its name by default, so both password and password_confirmation are already covered. If your password is using a differently named parameter, or if you want to protect other information (for example credit card numbers), you should add those parameter names to the special Rails configuration variable filter_parameters.

A Rails 4 project generated with the standard Rails generator will generate config/initializers/filter_parameter_logging.rb with the following line:

Rails.application.config.filter_parameters += [:password]

To protect another parameter, simply add it to the array, e.g.:

Rails.application.config.filter_parameters += [:password, :cc, :ccv]

15.3 SSL (Secure Sockets Layer)

So now our apps are secure, right? We properly encrypted our passwords in the database and we filtered sensitive data from being recorded in our logs. Well, we’re not quite finished with security yet. The password and other sensitive information is still vulnerable to eavesdropping while in-transit from the user’s browser to your web server.

To completely secure the information you need to use SSL (Secure Sockets Layer). Configuring and managing SSL for your web server is out of the scope of this book, but there are things to be done on the Rails side, which we will cover now.

First, set config.force_ssl = true in your configuration file to force all access to the application over SSL. Then specify use of Strict-Transport-Security HTTP header81 and secure cookies.

The force_ssl setting works by redirecting to an HTTPS URL with same parameters if you try to access the application via plain HTTP.


Trying to access a non-GET HTTP action with HTTP it might not actually work as you can not redirect to a POST request. The way to go is to use force_ssl on the GET request that renders the form. In which case standard form helpers will keep the HTTPS format for the form submit action.

If you want a fine grained control over the forcing of SSL connections, you can supply parameters to a controller’s force_ssl class method. It accepts the same kind of options as a before_action, as well as :host and :port options in case you need to specify a domain.

classUsersController < ApplicationController

force_ssl only: [:new, :edit], host: ""

If class-level options are not suitable for your application, you can always roll your own logic inside an action method. The ssl? method of a request option returns true if the request was received over an HTTPS connection.

15.4 Model mass-assignment attributes protection

Since its origins, Rails has featured a convenient mass-assignment feature allowing assignment of multiple model attributes by passing a hash of values. As such, it’s common to create a model using User.create(params[:user]) and to update it later using User.update(params[:user]).

Without protection direct mass-assignment access to all model attributes would be easy to exploit. For example, if you happen to define an is_admin boolean field in the “users” table, an attacker could give themselves admin privileges by sneaking along is_admin=true on an otherwise innocent registration form.

In the previous Rails versions, mass-assignment protection was implemented on the model level using attr_accessible and attr_protected class level methods.

In a nutshell, you could call attr_accessible with a list of model attributes to indicate that those attributes are safe to mass-update. attr_protected would do the opposite, disabling access to passed attributes. This is referred to as whitelisting and blacklisting, respectively.

There were several practical problems with the former approach:

· It was too cumbersome to use, as it restricted mass-assignment globally, including tests and access from other models. In those cases you usually know very well what attributes you are assigning and having to jump through hoops to do so. The result wasn’t very pleasant.

· Simple whitelisting and blacklisting didn’t allow for special cases where access to attributes depend on other attributes or other records, for example, user roles and permissions.

· Models don’t feel like the right place to do define these kinds of restrictions, since most of the time we only need restrictions on mass-assignment when passing unfiltered parameters to models inside a controller action method.

Rails 4 introduces a new and improved way of controlling mass-assignment attributes. The functionality was made available and proven in earlier Rails versions with the strong_parameters gem. The new approach forbids mass-assignment of a model attribute from a controller unless that attribute was white-listed.

The big difference is that whitelisting is configured using two simple methods (permit and require) that are exposed on a controller’s params hash. Calls to those methods can be chained to validate nested params hashes.

Calling require will validate that the parameter is actually present and throw an ActionController::ParameterMissing exception if it is not. It will also return the “extracted” value of the parameter.


An ActionController::ParameterMissing exception, unless unhandled, will bubble up to the Rails dispatcher and result in HTTP 400 Bad Request response.

Calling permit with a list of attributes will allow those attributes to “pass through” to the model during mass-assignment, but only if the value is one of the supported “scalar” types: String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO,ActionDispatch::Http::UploadedFile or Rack::Test::UploadedFile This restriction disables evil injection of arrays, hashes or any other objects.


:name, :email, :password, :password_confirmation)

Another option is to pass a hash. This will allow you to declare that one of the attributes can contain an array of scalar values:

params.permit(ids: [])

To whitelist all the attributes in a given hash call permit! method on it:


Using a combination of permit and require, it’s relatively easy to implement different parameter filtering options for creating new records and updating existing records, or any other “complicated” logic required:

Listing 15.1 A typical UsersController with param filtering

1 classUsersController < ApplicationController


3 def create

4 user = User.create!(create_params)

5 redirect_to user

6 end


8 def update

9 user = User.find(params[:id])

10 user.update!(update_params)

11 redirect_to user

12 end


14 private


16 def create_params

17 params.require(:user).permit(:name, :email, :password,

18 :password_confirmation)

19 end


21 def update_params

22 params.require(:user).permit(name: true, email: true, tags: [])

23 end

24 end

15.5 SQL Injection

SQL injection attacks were very popular when people wrote SQL code for their applications by hand. Even today, if you’re not careful, you can introduce code that is susceptible to this kind of attack.

15.5.1 What is a SQL Injection?

SQL injection is a catch-all description for attacks on SQL database-driven application. The attacker includes malicious fragments of SQL code in otherwise legitimate input provided to the application, in the hopes that the application “messes up” and sends those fragments along to the database to be executed.

Let’s see how this can happen. Suppose that we implemented product search functionality in our application using the following piece of code:

1 classProductsController < ApplicationController

2 def search

3 @products = Product.where("name LIKE '%#{params[:search_terms]}%'")

4 end

5 end

For a search string “test”, this code will execute the following SQL query:

SELECT * FROM products WHERE name LIKE '%test%';

Okay so far. But what if the user submits search_terms with a value of ';DELETE FROM users;

In this case, the resulting SQL code sent to the database is:

SELECT * FROM products WHERE name LIKE '%';DELETEFROM users;%';

That second statement will wipe out the entire ‘users’ table in the database.

Using variations on the same theme, an attacker could modify the users table to reset an administrator’s password, or retrieve data that he shouldn’t have access to.

To protect from this attack we could start escaping all the user input ourselves, but fortunately we don’t have to do that, as ActiveRecord already does it for us, we just need to know how to use it correctly.

The first rule to remember is to never directly inject user’s input into any string that will be used as a part of an SQL query, instead we should use variable substitution facility provided by ActiveRecord (and other object-mapping software, they all have it):

@products = Product.where('name LIKE ?', "%#{params[:query]}%")

The ‘?’ character in the query fragment serves as a variable placeholder. You can have more then one in any given query, just make sure to pass the right number of variables to interpolate.

You can read more about it this behavior in Chapter 5, “Working with Active Record”.

15.6 Cross-Site Scripting (XSS)

Cross-Site Scripting is one of the most common security vulnerabilities, but that doesn’t make it any less severe. When successfully exploited it can give an attacker a bypass around application authorization and authentication mechanisms and leak personal information.

The attack works by injecting a client-side executable code into the application pages. An example of such a code can be a JavaScript that “leaks” cookies to a remote server, which would allow the attacker to ‘clone’ any affected session. So if the attacker is able to lay his hands on the administrator session he would be able to impersonate administrator without actually passing the required authentication procedures, just by using already authenticated session.

There are several ways an attack code can “leak” the information. One of the simplest ones is to insert an image tag into the DOM with image reference to attacker’s server and image path including the leaked information. The attacher’s server access logs will capture the information where it can be retrieved later.

All recent versions of Rails make it relatively easy to avoid this kind of attack. In this section we will discuss the key elements provided by Rails to defend against XSS attacks and point out things to watch out for.

The most common mistake leading to an XSS vulnerability is failing to escape user input when rendering html. There are several possible vectors of attack for exploiting this mistake.

Attack code can be first saved into the database (like, for example, injecting it into a post title, or comment body, etc.), in which case such a database record becomes infected. Anyone visiting a page with infected data will run the malicious JavaScript code embedded in the record, allowing the attacker to access the visiting user’s session, and do whatever they’re allowed to do.

Another vector involves passing attack code as a URL parameter that is directly rendered into the page, causing the victim to visit an ‘infected’ URL.

In both cases the victim’s browser is exposed to the attack code which will execute in the browser’s context. The solution is to always “escape” or “sanitize” unsafe HTML content.

In this context, “escaping” means replacing some of the string characters by an HTML escape sequences that will remove the special meaning from the text and cause it to render as a regular text. Sanitizing on the other hand means validating the HTML content to ensure only “good” HTML tags and attributes are used.

Note that sanitizing is inherently less secure than escaping, and should only be used where rendered content must contain HTML markup. An example would be a WYSIWYG html editor on a textarea that manages code that is later rendered on a page.

15.6.1 HTML Escaping

In previous versions of Rails you had to think hard about escaping, and utilize the h view helper method to escape potentially unsafe content. Rails core fielded a lot of criticism for making our code “unsafe by default.” Having to think about escaping turns out to be very error-prone, and many developers forgot to do it properly. Recent versions of Rails (starting with 3.0) do a much better job. Every string is tagged as either safe or unsafe. All unsafe strings are automatically escaped by default. You only need to think about explicitly managing the “safeness” of strings when you’re writing helpers that output HTML into your template.

For obvious reasons, all Rails HTML helpers will output “safe” strings that can be directly rendered on a page. Otherwise you would have to call html_safe on the output of a helper.

For example lets look at the following view fragment:

%li= link_to, user_path(@user), class: user_class(@user)

The user’s name will be escaped and so will the return value of the user_class view helper method, (assuming it wasn’t tagged as safe.) The result of user_path(@user) is an unsafe string, so it will be escaped as well.

The net result of those changes in later versions of Rails is that it becomes easy to ensure proper HTML escaping. The “right thing” will be done in most cases, and Rails will play it safe by default. Occasionally Rails feels like it escapes “too much” when you forget to use html_safe on the return value of a custom view helper method. But the error is usually easy to spot.

Even though Rails is safe by default, you should still be very careful when you call html_safe though. Calling it on a unsafe input without validation will absolutely create an XSS vulnerability in your application.


The most common source of confusion about needing html_safe in view helper methods happens when manipulating literal strings.

1 def paragraphize(text)

2 text.split("\r\n\r\n").map do |paragraph|

3 content_tag(:p, paragraph)

4 end.join.html_safe

5 end

The call to content_tag on line 3 will properly escape its input, so we don’t have to manually escape paragraph. It is itself a view helper method, so it will tag its return value as html_safe. However join will join the safe strings from content_tag with an unsafe "" which is used as the default join string. You’ll scratch your head and wonder what’s going on, before adding the final html_safe in a state of confusion.

15.6.2 HTML Sanitization

In contrast to escaping, sanitization leaves some HTML intact. The idea is to only leave “safe” html tags that we want, and to remove all the rest. As usual with filtering problems, there are 2 approaches: blacklisting and whitelisting.

Blacklisting involves trying to detect and remove “bad” HTML fragments, like JavaScript tags or script content in links.

Whitelisting only allows HTML elements that are explicitly allowed, and escapes anything else.

Blacklisting is not a secure solution, since new hacks are being devised all the time and there’s no way we’d be able to be 100% sure that our blacklist is complete at all times. Therefore, we must use the whitelisting approach.

Rails has a SanitizeHelper module for “for scrubbing text of undesired HTML elements”. It includes several methods for our disposal that we already covered in Chapter 11, “All About Helpers”, so we won’t repeat them here.

15.6.3 Input vs Output Escaping

One more thing to discuss about HTML escaping is timing. When should we do it? On input of user data, or during rendering (output)?

The rule of thumb is to escape on output. The rationale being that we might want to render the content in different formats, and each has its own escaping requirements. For example, escaping HTML on input will not help us if the output format is JSON, which requires escaping of quote characters and not HTML tags.

Sanitization also mostly makes sense on output, as it will allow us to change the rules without re-applying them on all the data already stored.


Especially cautious application developers might decide to escape and sanitize on both input and output, but we find that it usually isn’t necessary.

15.7 XSRF (Cross-Site Request Forgery)

Cross-Site Request Forgery (usually abbreviated as CSRF or XSRF) is a type of web application vulnerability that allows an attacker to modify application state on behalf of a user that is logged into the application by luring the user to click on a carefully crafted link, visit a page, or even just open an email with malicious embedded images.

Assume that an intern named Frank at a banking institution implemented account fund transfer functionality as an HTTP GET method, like so:

1 GET /transfers?from_account_id=123&to_account_id=456&amount=1000

Note: You would NEVER do something like this in real life, this example is for illustrative purposes only. In fact, if you’re following proper RESTful practices, a GET would not make any modifications to server state. We’re about to see why…

Of course everyone, even interns, know you should authenticate banking transfers. So Frank does some research on Rails security and properly authenticates and authorizes the request.

You see the problem yet? No? Assume an end-user logs into his online banking, leaves it logged in and flips over to check his email in another browser tab. Even a relatively unsophisticated attacker could send him an HTML email with the following image:

<img src="http://banking-domain/transfers?from_account_id=<users_account_id>


It’s a long shot, but if this image is opened by victim’s browser while it is authenticated and authorized, the transfer will get executed because the session cookie from the bank is still valid.

Fortunately for the bank, Frank’s code was reviewed, and the reviewer pointed out the problem. So Frank fixed the problem by modifying his transfer action to use a POST instead of GET.

Are the bank’s customers safe yet? Not quite. An attacker can still “lure” victims to an innocent-looking site, which hosts JavaScript that will post to the fund transfer URL from within victim’s browser.

So how do we protect ourselves against this chicanery?

15.7.1 Restricting HTTP method for actions with side-effects

First we must only allow side effects on non-GET requests (e.g. POST, DELETE, PATCH). This is actually specified by HTTP protocol guidelines and there are several ways to do accomplish the restriction in Rails.

First, we can restrict the request methods at the routing level:

1 post 'transfers' => 'transfers#create'


3 resources :users do

4 post :activate, on: :member

5 end

Rails’ standard resources routing helper exhibits the correct behavior by default. It will require POST to access create, PATCH to access update, and DELETE to access destroy. You need to be careful when you define your own non-resource routes, especially if you use :action segment routes.

The truly paranoid among us can use a controller class method called verify to make sure that proper methods are used for controller actions with side-effects:

1 classUsersController < ApplicationController

2 verify method: [:post, :put, :delete], only: [:activate, :create,

3 :update], redirect_to: '/'

15.7.2 Require security token for protected requests

Using proper HTTP request method is not enough. We need to ensure that the requests originate form our application. You could check the referrer of HTTP requests, but the proper way to do it is to include a security token as a parameter or header on protected requests and validate the token on the server side.

Rails has built-in facilities to handle exactly this kind of security check. The boilerplate implementation of ApplicationController generated for new apps includes the following code:

1 classApplicationController

2 # Prevent CSRF attacks by raising an exception.

3 # For APIs, you may want to use :null_session instead.

4 protect_from_forgery with: :exception

5 end

This code adds a verify_authenticity_token before action callback to all requests in your application. The protect_from_forgery method takes :if/:except parameters just like a normal before_action declaration.

Additionally, the with parameter accepts one of the supported protection strategies: :exception, :null_session, or :reset_session.


raises ActionController::InvalidAuthenticityToken exception


Resets the user’s session

:null_session (default)

Executes the request as if no session exists. Used by default if no with parameter is supplied.

The difference between :reset_session and :null_session is that :null_session doesn’t actually changes the session, only substitutes an empty one for the current requests, while :reset_session will leave it empty for subsequent requests as well.

15.7.3 Client-side security token handling

Now that we are requiring a security token on the server side we need to pass it from the client side. Standard Rails form helpers (e.g. form_for) will include the token as a hidden parameter.

The same goes for the rails link helpers that generate non-GET Ajax requests (e.g. link_to with method: :post). Note that the actual handling of security tokens is done in the UJS JavaScript library, e.g.’jquery-rails’. You can check out the implementation of the handleMethod function injquery_ujs.js if you’re curious about it.

To function properly the browser needs access to the security token from the server. It is provided with a call to csrf_meta_tags in your application layout header section:



= csrf_meta_tags

This will render two meta tags:

<meta content="authenticity_token" name="csrf-param" />

<meta content="...." name="csrf-token" />

The actual token is stored in the session. It is generated for the first time when it is needed, and preserved for the duration of the session. The call to csrf_meta_tags is included in the boilerplate application template of a fresh Rails app.

15.8 Session Fixation Attacks

A session fixation attack is something to be aware of if you implement your own session management. The Rails cookies session store is immune from these types of attacks.

Many session security implementations depend on the session id being a secret. If the attacker is successfully able to force a user to use their session id and login into the system, the attacker can get access to the authenticated session by using that id.

Session fixation attacks are only possible when hackers are able to force the setting of a third session id in the user’s browser through a URL or other means. For example, in some configurations of PHP, you can allow a session id to be passed as a URL parameter called _my_app_session_id. The attacker can send victims to the malicious link, which then redirects back to the target system including a session id that they generated.

Defending against this hack is pretty simple. Whenever you elevate a user’s privileges, call the reset_session helper, which ensures that their session id is changed. Attackers are left with an old unauthenticated session.


Any decent Rails authentication system, like Devise, already protects you from session fixation attacks. So you don’t usually need to worry about it unless you are doing something unusual.

15.9 Keeping Secrets

As a general rule, you should not store secret things in your source code. This includes passwords, security tokens, API keys etc. Assume that a determined attacker will gain access to your source code and use it to their advantage, if they can.

So where do you store secret parts of your application’s configuration, including API keys and tokens for external services? The recommended way is to get those from your shell environment.

For example, let’s say you need to configure a Pubnub service. The following code will allow you to configure Pubnub using 5 environment variables. (Put it in config/initializers/pubnub.rb)






6 ENV["PUBNUB_SSL"] == "true")

If you deploy to Heroku you can easily configure environment variables using heroku command line tool:

$ heroku config:add PUBNUB_PUBLISH_KEY=.... PUBNUB_SUBSCRIBE_KEY=... ...

Other deployment options should allow you to define environment variables easily since it’s a common need.

Even if you have no easy way to control environment directly, you almost always have a way to add extra files to the deployment directory. You can load such a file into your environment like this (add this code to the top of your config/application.rb):

1 # change this path according to your needs

2 ENV_PATH = File.expand_path('../env.rb', __FILE__)

3 require ENV_PATH if File.exists?(ENV_PATH)

The env.rb file can assign environment variables as needed:


IMPORTANT: Rails by default stores a very important secret in the source code. Take a look at config/secrets.yml:

Rails.application.secrets.secret_key_base = '...'

Change this to the following:

1 # config/secrets.yml


3 ...


5 production:

6 secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

This token is used to sign the session cookie, and it allows anyone that has it to modify session to their liking, bypassing most security measures.

15.10 Conclusion

Security is a topic that should never be taken lightly, especially when developing business-critical applications. Since exploits are always being discovered, it’s very important to keep up-to-date on new developments. We recommend that you check out and for the latest information available.

Finally, you should consider using Code Climate to automatically analyze and audit your Rails code after every git push. Tell Bryan and Noah that The Rails 4 Way crew sent you.