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

CWE-916: Using Potentially Dangerous Functions in Rails


In the hands of a skilled chef, a sharp knife is a tool of precision and creativity. It can be used to carve a delicate garnish, chop vegetables with speed and accuracy, or butcher a large cut of meat. The knife itself isn’t inherently “good” or “bad”—it’s a powerful, versatile tool. In the hands of a novice, though, or used carelessly, that same knife can cause serious harm.

The world of software development has its own set of “sharp knives”: powerful functions and methods that, in the right context, are incredibly useful. Ruby, with its dynamic nature and metaprogramming capabilities, has more of these than most languages. When used with care, they allow for elegant and flexible code. When mishandled, however, they can introduce security vulnerabilities.

This brings us to the Common Weakness Enumeration (CWE), a community-developed list of software and hardware weakness types. It serves as a common language for security professionals and developers. One such weakness, CWE-916: Use of Potentially Dangerous Function, is particularly relevant to us as Ruby on Rails developers. It describes this exact scenario: using a function that, while not inherently dangerous, can be misused to create a security risk.

In this article, we’ll provide a security overview of CWE-916 in the context of Ruby on Rails. We’ll look at a few of these “sharp knives” in the Rails toolbox, understand the risks they pose, and discuss how to handle them safely.

Understanding the Risk: From Convenience to Vulnerability

When we use potentially dangerous functions with untrusted input, we expose our application to severe security vulnerabilities, such as:

  • Arbitrary Code Execution: Allowing an attacker to run arbitrary code on your server.
  • Denial of Service (DoS): Making your application unavailable to legitimate users.
  • Information Disclosure: Exposing sensitive data.
  • Object-Relational Mapping (ORM) Injections: Leading to unauthorized data access or manipulation.

These functions are often provided as conveniences, but that convenience can turn into a vulnerability if we’re not careful. To make this more concrete, let’s explore some of the most common functions in Rails that we should handle with care.

The Double-Edged Sword of constantize

The constantize method in Active Support is a powerful tool that converts a string into a constant. We often use it for dynamic class loading based on parameters. However, if an attacker can control the input to constantize, they could instantiate any class in our application’s scope, leading to a variety of attacks.

Insecure Example:

class ReportController < ApplicationController
  def generate
    report_type = params[:type].constantize
    report = report_type.new(report_params)
    report.generate
    # ...
  end
end

In this example, an attacker could pass params[:type] as "User" to instantiate a User object, or worse, a class that performs a sensitive operation upon initialization.

Mitigation:

To safely use constantize, we should restrict the input to a known set of allowed classes:

class ReportController < ApplicationController
  ALLOWED_REPORTS = ["SalesReport", "UserDataReport"]

  def generate
    report_class_name = params[:type]
    if ALLOWED_REPORTS.include?(report_class_name)
      report_type = report_class_name.constantize
      report = report_type.new(report_params)
      report.generate
      # ...
    else
      render json: { error: "Invalid report type" }, status: :bad_request
    end
  end
end

YAML.load: Deserialization Dangers

The YAML.load method is used to deserialize YAML documents. Prior to Ruby 2.1, YAML.load could deserialize arbitrary objects, which could lead to remote code execution if the YAML source is controlled by an attacker. Of course, newer versions of Ruby have introduced YAML.safe_load as a more secure alternative, but we might still encounter YAML.load in older applications.

Insecure Example:

require 'yaml'

# Attacker-controlled YAML
yaml_data = "--- !ruby/object:Gem::Installer\n    i: x\n"

# Insecure deserialization
YAML.load(yaml_data)

This YAML payload could trigger a remote code execution vulnerability.

Mitigation:

We should always use YAML.safe_load when parsing YAML from untrusted sources. YAML.safe_load restricts the types of objects that can be deserialized to basic types, preventing arbitrary object creation.

Tip: If you need to deserialize more complex, trusted objects, you can use YAML.safe_load with the permitted_classes option. This gives you more control than YAML.load while still being safer.

require 'yaml'

# Attacker-controlled YAML
yaml_data = "--- !ruby/object:Gem::Installer\n    i: x\n"

# Secure deserialization
begin
  data = YAML.safe_load(yaml_data)
rescue Psych::DisallowedClass
  # Handle the error gracefully
  puts "Error: Potentially dangerous YAML content."
end

send: A Method with Too Much Power?

The send method is a powerful feature of Ruby’s metaprogramming capabilities, allowing us to call a method by its name as a string. However, if an attacker can control the method name passed to send, they can call any method on the object, including private methods or methods that should not be accessible.

Insecure Example:

class UserController < ApplicationController
  def update
    user = User.find(params[:id])
    user.send(params[:action_to_perform], params[:data])
    # ...
  end
end

An attacker could use this to call sensitive methods like destroy or update_attribute with arbitrary data.

Mitigation:

Similar to constantize, we should whitelist the allowed methods that can be called via send:

class UserController < ApplicationController
  ALLOWED_ACTIONS = [:update_profile, :change_password]

  def update
    user = User.find(params[:id])
    action = params[:action_to_perform].to_sym
    
    if ALLOWED_ACTIONS.include?(action)
      user.send(action, params[:data])
      # ...
    else
      render json: { error: "Invalid action" }, status: :bad_request
    end
  end
end

Principles for Safe Code

  • Avoid Metaprogramming with User Input: We should be cautious when using metaprogramming techniques that rely on user-supplied data.
  • Prefer Whitelisting over Blacklisting: We should always validate user input against a list of known, safe values.
  • Use Secure Alternatives: When available, we should use safer alternatives to dangerous functions, such as YAML.safe_load.
  • Stay Updated: We need to keep our Rails and Ruby versions up to date to benefit from the latest security patches.
  • Static Analysis Tools: We can use tools like Brakeman to scan our code for potential security vulnerabilities, including the misuse of dangerous functions.

Conclusion

CWE-916 highlights the importance of understanding the tools we use and their potential for misuse. While functions like constantize, YAML.load, and send are powerful, they require careful handling to prevent security vulnerabilities. By following the best practices we’ve outlined in this article, we can write more secure and robust Ruby on Rails applications.

For more information on CWE-916 and other security weaknesses, visit the CWE website. To keep your Rails application secure, consider our Ruby on Rails upgrade services.

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