The go-to resource for upgrading Ruby, Rails, and your dependencies.

Improper Access Control in Rails: Preventing IDOR Vulnerabilities (CWE-284)


Improper Access Control, also known as Insecure Direct Object Reference (IDOR), is a common and serious vulnerability that can lead to unauthorized access to data. The Common Weakness Enumeration (CWE) system identifies this vulnerability as CWE-284: Improper Access Control. For Ruby on Rails developers, preventing IDOR is a critical part of building secure applications that protect user data.

This article will explain what IDOR is, provide examples of how it can occur in a Rails application, and detail the best practices for preventing it.

What is an IDOR Vulnerability?

An IDOR vulnerability occurs when an application provides direct access to objects based on user-supplied input. If the application does not verify that the user is authorized to access the requested object, an attacker can manipulate the input to access data they shouldn’t be able to see.

For example, consider a URL like https://example.com/invoices/123, where 123 is the ID of an invoice. If an attacker changes the ID to 124, and the application doesn’t check if the currently logged-in user owns invoice 124, it will display the invoice. This is an IDOR vulnerability.

IDOR in Rails: A Vulnerable Example

Let’s look at a typical Rails controller action that could be vulnerable to IDOR:

class InvoicesController < ApplicationController
  before_action :authenticate_user!

  def show
    # Vulnerable: No authorization check
    @invoice = Invoice.find(params[:id])
  end
end

In this example, the authenticate_user! before_action ensures that only logged-in users can access the show action. However, it doesn’t check if the user is authorized to view the specific invoice requested. If a user is logged in, they can change the id in the URL to view any invoice in the database.

Preventing IDOR Vulnerabilities in Rails

The key to preventing IDOR is to always verify that the current user is authorized to access the requested resource. This should be done on the server-side, in your controllers or models.

Scoping Queries to the Current User

The most effective way to prevent IDOR is to scope your database queries to the currently authenticated user. This ensures that you only ever access data that belongs to that user.

Here’s how to fix the vulnerable InvoicesController from the previous example:

class InvoicesController < ApplicationController
  before_action :authenticate_user!

  def show
    # Safe: Scoped to the current user
    @invoice = current_user.invoices.find(params[:id])
  end
end

In this corrected version, we are no longer calling Invoice.find. Instead, we are calling current_user.invoices.find. This assumes you have a has_many :invoices association on your User model. This change ensures that even if an attacker manipulates the id in the URL, the query will only find invoices that belong to the current_user. If the invoice doesn’t exist for that user, ActiveRecord will raise a RecordNotFound exception, and the attacker will receive a 404 error.

Using Authorization Libraries

For more complex applications, you may want to use an authorization library like Pundit or CanCanCan. These libraries provide a more structured way to manage authorization logic and can help you prevent IDOR vulnerabilities.

Here’s an example of how you might use Pundit to authorize access to an invoice:

class InvoicesController < ApplicationController
  before_action :authenticate_user!

  def show
    @invoice = Invoice.find(params[:id])
    authorize @invoice
  end
end

In this example, the authorize method (provided by Pundit) will call an InvoicePolicy to determine if the current_user is allowed to view the @invoice. If not, it will raise an exception.

Avoid Exposing Database IDs

Another way to make IDOR attacks more difficult is to avoid exposing sequential database IDs in your URLs. Instead, you can use non-sequential identifiers like UUIDs or slugs.

You can use the SecureRandom module in Ruby to generate unique tokens for your models:

class Invoice < ApplicationRecord
  before_create :generate_token

  private

  def generate_token
    self.token = SecureRandom.uuid
  end
end

Your URL would then look something like this: https://example.com/invoices/a1b2c3d4-e5f6-7890-1234-567890abcdef. While this doesn’t replace the need for proper authorization checks, it makes it much harder for an attacker to guess the IDs of other objects.

Tools for Detecting IDOR Vulnerabilities

While manual code review is the most effective way to find IDOR vulnerabilities, you can also use security scanning tools to help you identify potential issues.

Brakeman

Brakeman is a static analysis tool that can detect many types of security vulnerabilities in Rails applications, including some forms of improper access control.

Dynamic Application Security Testing (DAST)

DAST tools can scan your running application for vulnerabilities by attempting to exploit them. These tools can be effective at finding IDOR vulnerabilities by manipulating parameters and analyzing the responses from the application.

Conclusion

IDOR vulnerabilities are a serious threat to the security of your Rails application. However, by following a few key principles, you can effectively prevent them:

  • Always authorize access to resources on the server-side.
  • Scope your database queries to the currently authenticated user.
  • Use authorization libraries like Pundit or CanCanCan for complex applications.
  • Consider using non-sequential IDs to make it harder for attackers to guess object IDs.
  • Regularly review your code and use security scanning tools to identify potential vulnerabilities.

By implementing these practices, you can build more secure Rails applications and protect your users’ data from unauthorized access.

Sponsored by Durable Programming

Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.

Hire Durable Programming