Ruby on Rails Security Basics
This getting started document explains how to use the built in security of Ruby on Rails to build secure web applications and prevent the most common vulnerabilities, such as cross-site scripting and sql injections. The guide also lists down a number of Ruby gems that can be used to help developers write more secure code.
Your Information will be kept private.
Your Information will be kept private.
Recently, I have been working on my first Rails application. Even though I have been working with Ruby for a number of years, this is the first time I’ve ever developed an application using the Rails framework. By trade, I’m a security tester, however, I do like to work on software projects in order to keep my skills sharp and practice what I preach.
Rails has some security features built-in and enabled by default, however, I also recommend installing some additional gems, which cover web application security features that Rails lacks by default. This article explains what are the basic Ruby on Rails built-in security features and the gems that I recommend to install.
Ruby on Rails Built-in Security Features
I’m a great believer in secure-by-default and making security easy for developers. Some may argue that by making it easy, it will make developers pay less attention to security and possibly lead them to making more security bugs. Kind of like a horse with blinkers on. In reality, I think it probably requires good balance: don’t make security invisible to the developer but instead, make it just easy enough for them to implement correctly.
So be warned! Don’t just rely on Rails built-in security features thinking that they offer a 100% effective way of mitigating the security vulnerabilities that they were designed to prevent. Instead, learn how to use them correctly and know their limitations.
Preventing Cross-Site Scripting (XSS)
To help prevent Cross-Site Scripting (XSS) vulnerabilities, we sanitize input and encode output using correct encoding for the output context.
Sanitizing Input
Rails makes sanitizing user input easy with its Model View Controller (MVC) design. Any data stored or retrieved from a database should pass through a model, so this is a great place to sanitize our stored data. Using Active Record Validations within our models, we can ensure that data is present and/or in a specific format.
You can also sanitize input/output within your view using the sanitize method. The sanitize method will HTML encode all tags and strip all attributes that aren’t specifically allowed. Let’s pass it a common JavaScript XSS payload and see how it reacts:
<%= sanitize '<img src=x onerror=prompt(1)>' %>
The above will output:
<img src="x">
As we can see, the sanitize method has allowed our img tag with the src attribute, but it has removed the onerror event attribute. By default, if we don’t whitelist which tags/attributes we want, Rails will make that decision for us on what it believes is safe.
If we whitelist the src and onerror attributes, our XSS payload is executed:
<%= sanitize '<img src=x onerror=prompt(1)>', attributes: %w(src onerror) %>
The above will output:
<img src="x" onerror="prompt(1)">
Encoding Output
In modern versions of Rails, strings output in the view are automatically encoded. However, there may be occasions when you require to encode HTML output by yourself. The main output encoding method in rails is called html_escape, you can also use h() as an alias. The html_escape method escapes HTML tag characters. Let’s pass it a common XSS payload and see how it reacts:
<%= html_escape '<img src=x onerror=prompt(1)>' %>
The above will output:
<img src=x onerror=prompt(1)>
As we can see, the html_escape method has converted the < and > characters into HTML entities, ensuring the browser does not interpret them as markup.
This is the same output as we would see if we simply passed a string, thanks to the default encoding in Rails:
<%= "<img src=x onerror=prompt(1)>" %>
The above will output:
<img src=x onerror=prompt(1)>
But don’t forget what we said earlier! Just because modern versions of Rails encode strings in views by default, it does not mean that XSS attacks cannot happen. One example is within the href value of a link (using the link_to method).
Preventing Cross-Site Request Forgery (CSRF)
Modern versions of Rails protect against CSRF attacks by default by including a token named authenticity_token within HTML responses. This token is also stored within the user’s session cookie – when a request is received by Rails, it checks one against the other. If they do not match, an error is raised.
It is important to note that the CSRF protection in Rails does not apply to GET requests. GET requests should not be used to change the application state anyway and should only be used to request resources.
Although enabled by default, you can double-check that it’s enabled by seeing if the protect_from_forgery method is within the main ApplicationController.
Preventing SQL Injection
Rails uses an Object Relational Mapping (ORM) framework called ActiveRecord to abstract interactions with a database. ActiveRecord, in most cases, protects against SQL Injections by default. However, there are ways how it can be used insecurely, which can lead to security issues.
Using ActiveRecord, we can select the user with the supplied id and retrieve that user’s username:
User.find(params[:id]).username
The above will return the username of the user whose user id matches the one supplied via the params hash. Let’s take a look at the SQL query generated by the code above on the backend:
SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
As we can see from the SQL query above, when using the find method on the User object ActiveRecord is binding id to the SQL statement, protecting us from SQL Injection.
What if we wanted to select a user which matches a username and password, commonly seen in authentication forms? You might see something like this:
User.where("username = '#{username}' AND encrypted_password = '#{password}'").first
If we supply a username with the value ‘) OR 1– the corresponding SQL query on the backend becomes:
SELECT "users".* FROM "users" WHERE (username = '') OR 1--' AND encrypted_password = 'a') ORDER BY "users"."id" ASC LIMIT 1
By injecting our specially crafted SQL, what we have done is told the database to return all rows from the users table where the username is null or true. This makes the SQL statement return true along with all of the data in the users table.
For some great examples of how not to use ActiveRecord, here’s a great resource, which I suggest you check regularly to ensure that you don’t have any of such examples within your code – http://rails-sqli.org/.
Ruby on Rails Security Gems
As we have seen, Rails offers many built-in security features to help protect our applications, sensitive data, and users from web-based attacks. But we also saw that these have their limitations. For security features that Rails does not offer by default, there are always open source options: gems, lots and lots of gems. Here are some of my favorites that you can put in your Gemfile.
Devise
Devise is a popular access control, authentication, and authorization gem for Rails. It offers secure password storage using bcrypt to hash salted passwords, user lockouts, user registration, forgotten password functionality, and more.
Although Devise’s own README states “If you are building your first Rails application, we recommend you to not use Devise”, I would ignore this statement. If you’re security-aware and have built applications in other frameworks before, I don’t see any issue with using Devise for your first Rails application.
URL: https://github.com/plataformatec/devise
Brakeman
Brakeman is a Static Code Analysis tool for Rails applications. It searches your application’s source code for potential vulnerabilities. Although it does report the occasional false positive, personally, I think this is a great gem and one that I would definitely recommend running against your application before going into production. Even better, run it after every commit.
URL: https://github.com/presidentbeef/brakeman
Secure Headers
Developed by Twitter, SecureHeaders is a Gem that implements security-related HTTP headers into your application’s HTTP responses. Headers such as Content Security Policy to help protect against Cross-Site Scripting (XSS) attacks, HTTP Strict Transport Security (HSTS) to ensure your site is only accessible over secure SSL connections, X-Frame-Options, and others.
URL: https://github.com/twitter/secureheaders
Rack Attack
Developed by Kickstarter, Rack::Attack is a Gem for blocking & throttling abusive requests. Personally, I use Rack::Attack to prevent form abuse. For example, instead of implementing a CAPTCHA on a submission form, I use Rack::Attack to ensure it is not submitted too many times in a short space of time. This should prevent automated tools from abusing the form submission. It also supports whitelisting and blacklisting of requests.
URL: https://github.com/kickstarter/rack-attack
Codesake Dawn
Codesake::Dawn is similar to Brakeman in that it scans your source code for potential vulnerabilities. However, Codesake::Dawn also has a database of known vulnerabilities, which it uses to scan your Ruby, Rails, and gems for known issues.
URL: https://github.com/codesake/codesake-dawn
Ruby on Rails Code Quality Gems
Sloppy and messy code leads to bugs and some bugs may have security implications. Better quality code means more secure code. Let’s take a look at what gems we can use to ensure our code is nice and clean.
Rails Best Practices
The Rails best practices gem is a great gem for ensuring that your code adheres to best practices. It will help you make your code more readable and eloquent by scanning through it and giving you suggestions on how to improve the syntax.
URL: https://github.com/railsbp/rails_best_practices
Rubocop
Rubocop is not specific to Rails and can be used for any Ruby application. It uses the Ruby Style Guide as a reference to scan your code and ensure that you adhere to it. This includes tings like variable naming, method size, using outdated syntax, etc.
URL: https://github.com/bbatsov/rubocop
Conclusion
Rails does a lot of things right when it comes to security. When you develop a Rails application, it feels like Rails has your back. However, don’t let this sense of security lull you into a state of not caring about security. As we have seen, there are pitfalls, and it only takes one mistake for your sensitive information such as credit card data to end up on pastebin.
No post on the Netsparker blog would be complete without a Netsparker plug. Everything we’ve talked about in this post is mostly related to the source code. As well as looking at your application’s source code, you should also ensure that it is scanned with a heuristic scanner like Netsparker. You can take every development precaution, read every line of code, but this does not mean that you will catch every single vulnerability. Netsparker web vulnerability scanner should be used in conjunction with what has been discussed above throughout your Security Development Lifecycle, as well as when in production.
Further Reading
Below is a list of URLs from where you can find more information about Ruby on Rails security and best coding practices.
http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html
http://guides.rubyonrails.org/active_record_validations.html
http://api.rubyonrails.org/classes/ERB/Util.html#method-c-html_escape
http://guides.rubyonrails.org/security.html
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html