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

CWE-78 Command Injection in Rails: Securely Handling External Commands


Command injection is a security vulnerability that allows an attacker to execute arbitrary commands on the host operating system. This vulnerability can occur when an application passes unsanitized user-supplied data to a system shell. In a Ruby on Rails application, this can happen when using methods that execute external commands, such as system, exec, or backticks (`).

This article will explore how command injection vulnerabilities can occur in Rails applications and how to prevent them by securely handling external commands.

Understanding Command Injection

Command injection is classified as CWE-78 in the Common Weakness Enumeration list. It is a serious vulnerability that can lead to a complete compromise of the server. An attacker could use command injection to:

  • Read, write, or delete files on the server
  • Install malware or ransomware
  • Gain a reverse shell to the server
  • Pivot to other servers on the network

Insecure Handling of External Commands

Let’s look at an example of how command injection can occur in a Rails application. Imagine we have a feature that allows users to look up the IP address of a domain name using the ping command.

class PingController < ApplicationController
  def lookup
    domain = params[:domain]
    @result = `ping -c 1 #{domain}`
    render "lookup"
  end
end

In this example, the domain parameter is taken directly from the user’s request and concatenated into the ping command. An attacker could abuse this by providing a malicious domain name that includes shell commands. For example, an attacker could provide the following domain:

example.com; ls -la

The command that would be executed on the server would be:

ping -c 1 example.com; ls -la

This would first ping example.com and then list all the files in the current directory. The output of the ls -la command would be displayed to the attacker.

Securely Handling External Commands

To prevent command injection, you should never pass unsanitized user-supplied data to a system shell. The best way to avoid command injection is to avoid calling external commands altogether. However, if you must call an external command, you should use a method that does not invoke a shell, or you should sanitize the user-supplied data.

Use system with multiple arguments

In Ruby, the system method can be used to execute an external command. When you pass a single string to system, it is executed in a shell. However, when you pass multiple arguments to system, the command is executed directly without a shell. This is the recommended way to execute external commands in Ruby.

class PingController < ApplicationController
  def lookup
    domain = params[:domain]
    @result = system("ping", "-c", "1", domain)
    render "lookup"
  end
end

In this example, the ping command is executed with the arguments -c, 1, and the domain parameter. Because the command is not executed in a shell, an attacker cannot inject shell commands.

Use Open3.capture3

Another way to securely execute external commands is to use the Open3.capture3 method. This method is similar to system in that it can execute a command with multiple arguments, but it also allows you to capture the standard output, standard error, and exit status of the command.

require 'open3'

class PingController < ApplicationController
  def lookup
    domain = params[:domain]
    stdout, stderr, status = Open3.capture3("ping", "-c", "1", domain)
    @result = stdout
    render "lookup"
  end
end

Sanitize user input

If you must use a method that invokes a shell, you should sanitize the user-supplied data to ensure that it does not contain any shell metacharacters. The Shellwords.escape method can be used to escape any characters that have a special meaning to the shell.

require 'shellwords'

class PingController < ApplicationController
  def lookup
    domain = Shellwords.escape(params[:domain])
    @result = `ping -c 1 #{domain}`
    render "lookup"
  end
end

In this example, the Shellwords.escape method is used to escape any shell metacharacters in the domain parameter. This prevents an attacker from injecting shell commands.

Conclusion

Command injection is a serious vulnerability that can lead to a complete compromise of your server. To prevent command injection in your Rails applications, you should always handle external commands securely. The best way to do this is to use methods that do not invoke a shell, such as system with multiple arguments or Open3.capture3. If you must use a method that invokes a shell, you should sanitize the user-supplied data using the Shellwords.escape method.

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