Ruby on Rails Security Guide (Tutorial)

Photo of Mateusz Pałyz

Mateusz Pałyz

Updated Aug 3, 2023 • 7 min read

Security is one of the most important aspects of any web application. What’s really cool about Ruby on Rails is that it comes with a great setup and techniques to protect against most attacks.

Let’s focus on three types of attacks and countermeasures that we have up our sleeve. To make it more fun we’ll check them out on an actual application and we’ll also see what happens if we decide to be vulnerable and get hacked.

Is Ruby on Rails secure?

Like with any framework, the answer to that question is complicated. When using Rails correctly you can build secure apps as it has many features built right into the core functionality. Security, however, is much more than just using a given framework, it mostly depends on developers using the framework and development methods. Let's dive into some examples.

Sample application

Clone the repository https://github.com/mateuszpalyz/rails_security and follow setup guidelines from README.

Now, at http://localhost:3001 we have a sample website where we can start hacking.

CSRF

As the Rails guides say, Cross-Site Request Forgery is an attack which “works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands.”

So what does all that actually mean? Let’s imagine that we have a controller that implements a method for removing a given resource. What would happen if an attacker creates a link that uses that method and convinces us to click on that link, e.g. from email. In this case, Rails default settings kick in and we would get a CSRF token mismatch. That token is included in requests and then validated on the server side.

Of course, we can turn that protection off. Actually, playing with "skip_before_action :verify_authenticity_token" without a deeper understanding of what one is doing is quite a common issue.

Let’s see a live example, visit http://localhost:3001/csrf. This page displays some records from a database and we also have a link to send an email prepared by an attacker. Let’s click that link (it will open up in a browser thanks to letter_opener gem). In the mail, we have two links, one to destroy the action that uses default settings with CSRF protection turned on, and the other one which skips CSRF protection:

  protect_from_forgery except: :destroy_csrf_off
  
  def destroy_csrf_off
    destroy
  end

  def destroy_csrf_on
    destroy
  end
  
  def destroy
    @some_record = SomeRecord.find(params[:id])
    @some_record.destroy

    redirect_to csrf_path
  end

from https://github.com/mateuszpalyz/rails_security/blob/master/app/controllers/pages_controller.rb

When we click on the safe link we get when an exception but when we click on the other link, a record from the some_records table is removed. 😨

Cross-Site Scripting (XSS)

Again, quoting the Rails guide on Cross-Site Scripting, “This malicious attack injects client-side executable code.” How can an attacker inject that code? Basically, any place with user input can be vulnerable. Most often XSS can happen in WYSIWYG editors that don't correctly escape input submitted by a user. When code is injected, the attacker can run any malicious code they can think of, e.g. steal cookies. Usually, we're speaking about JavaScript injection but CSS is also vulnerable. Rails protects us from this kind of attack with a user input sanitizer. Let’s see that in action. Visit http://localhost:3001/xss, where we have a string that is potentially dangerous:

<script>alert('oh no, xss :(')</script>
The first link leads to a page where this string is not escaped:
<%= "<script>alert('oh no, xss :(')</script>".html_safe %>

from https://github.com/mateuszpalyz/rails_security/blob/master/app/views/pages/xss_vulnerable.html.erb#L15

This causes malicious code to be executed.

The second link leads to a page where the string is escaped and simply displayed but no code is executed.

<%= "<script>alert('oh no, xss :(')</script>" %>

from https://github.com/mateuszpalyz/rails_security/blob/master/app/views/pages/xss_free.html.erb#L15

SQL injection

The Rails guide warns, “SQL injection attacks aim at influencing database queries by manipulating web application parameters. A popular goal of SQL injection attacks is to bypass authorization. Another goal is to carry out data manipulation or reading arbitrary data.” This is a serious one and endangers not only the data of a single user but the whole database with any sensitive data that we have there. Let’s dive into an example.

Visit http://localhost:3001/sql_injection. Here, we have a page that displays records from the some_records table (name and priority), and below we have a search input that unfortunately is vulnerable to injection.

@some_records = SomeRecord.where("name like '%#{params.dig(:query, :name)}%'") # with sql injection

from https://github.com/mateuszpalyz/rails_security/blob/master/app/controllers/pages_controller.rb#L39

We interpolate user input directly into the query. If we were to use

@some_records = SomeRecord.where('name like ?', "%#{params.dig(:query, :name)}%") # without sql injection

from https://github.com/mateuszpalyz/rails_security/blob/master/app/controllers/pages_controller.rb#L38 that would have escaped user input and made the query secure (you can test that out later by uncommenting that line and commenting out the L39).

Let’s be nasty now, we could drop the whole some_records table but there will be more fun with getting data out of some other table. Here are some clues: union select can be used here, the other table is called some_other_records and it has the same number of columns as some_records -> 5. Happy hacking! If you need more help, a sample solution is hidden at the bottom of the page, just copy it into the search input.

Summary

As usual, with Rails we have a solid foundation thanks to which we can keep our applications secure. What is important is to know what kind of attack might be performed, what the countermeasures are, and not to fight with what Rails gives us out of the box. That way we can build secure apps. Stay safe.

Photo of Mateusz Pałyz

More posts by this author

Mateusz Pałyz

Mateusz started his adventure in IT at the university where he studied computer networks. Soon, he...

Read more on our Blog

Check out the knowledge base collected and distilled by experienced professionals.

We're Netguru

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency.

Let's talk business