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