Ruby on Rails Security Guide (Tutorial)

Photo of Mateusz Pałyz

Mateusz Pałyz

Updated Dec 2, 2024 • 14 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.

Additionally, configuring strict transport security in the environment files, such as 'production.rb', ensures that all requests are served over secure channels, reducing the risk of man-in-the-middle 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.

Introduction to Ruby on Rails Security

Ruby on Rails is a popular web application framework that provides a robust set of tools and features to build secure web applications.

However, security is a shared responsibility between the framework and the developer. Rails comes with a variety of built-in security features designed to protect your web application from common threats.

These include protections against SQL injection, Cross-Site Scripting (XSS), and Cross-Site Request Forgery (CSRF), among others. But leveraging these features effectively requires a good understanding of both the framework and secure coding practices. In this section, we will introduce the basics of Ruby on Rails security and provide an overview of the security features and best practices that can be used to protect web applications from common security threats.

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. Implementing robust authorization mechanisms for access control is crucial to prevent unauthorized access to sensitive resources and data. Rails provides built-in mechanisms to control access to specific resources and actions within an application.

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.

Understanding Security Threats

Security threats are a major concern for web applications, and Ruby on Rails is no exception. Some common security threats that affect Ruby on Rails applications include:

  • Cross-Site Scripting (XSS): XSS is a type of attack that occurs when an attacker injects malicious code into a web page, which is then executed by the user’s browser. This can lead to unauthorized actions, data theft, and more.

  • Cross-Site Request Forgery (CSRF): CSRF is a type of attack that occurs when an attacker tricks a user into performing an unintended action on a web application. This can happen if a user is authenticated and the attacker sends a request that the web server trusts.

  • SQL Injection: SQL injection is a type of attack that occurs when an attacker injects malicious SQL code into a web application’s database. This can lead to unauthorized access to sensitive data, data manipulation, or even complete database compromise.

  • Sensitive Data Exposure: Sensitive data exposure occurs when an application fails to properly protect sensitive data, such as user passwords or credit card numbers. This can result in data breaches and significant harm to users.

Understanding these threats is the first step in protecting your web application. In the following sections, we will explore how to mitigate these risks using Ruby on Rails.

Secure Coding Practices

Secure coding practices are essential to building robust and secure web applications in Ruby on Rails. Here are some best practices to follow:

  • Validating and Sanitizing User Input: Always validate and sanitize user input to prevent XSS and SQL injection attacks. Rails provides built-in helpers to sanitize input and ensure that only safe data is processed.

  • Using Secure Password Storage: Store passwords securely using a strong hashing algorithm, such as bcrypt. This ensures that even if your database is compromised, the passwords remain protected.

  • Implementing Access Controls: Implement access controls to restrict access to sensitive data and functionality. Use Rails’ built-in mechanisms to define and enforce user roles and permissions.

  • Using Secure Communication Protocols: Use secure communication protocols, such as HTTPS, to protect data in transit. This ensures that data exchanged between the client and server is encrypted and secure from eavesdropping.

By following these practices, you can significantly reduce the risk of security vulnerabilities in your web applications.

Security Testing and Maintenance

Security testing and maintenance are essential to ensuring the security of a Ruby on Rails application. Here are some best practices to follow:

  • Conducting Regular Security Audits: Regular security audits should be conducted to identify and address security vulnerabilities. This involves reviewing your codebase, configurations, and dependencies for potential issues.

  • Implementing Security Testing: Implement security testing to identify and address security vulnerabilities. Use tools and techniques such as static code analysis, penetration testing, and automated security scanners.

  • Keeping Dependencies Up to Date: Keep your dependencies up to date to ensure that any known security vulnerabilities are addressed. Regularly check for updates and apply patches as needed.

  • Monitoring for Security Incidents: Monitor your application for security incidents and respond promptly and effectively. Set up logging and alerting mechanisms to detect and respond to suspicious activities.

By incorporating these practices into your development workflow, you can maintain a high level of security for your Ruby on Rails applications.

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 theRails 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. Visithttp://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.

Visithttp://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

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. Implementing rate limiting and throttling mechanisms is crucial to protect against denial of service attacks. 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...
Efficient software development  Build faster, deliver more  Start now!

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