Command injection prevention for Ruby
This is a command injection prevention cheat sheet by Semgrep, Inc. It contains code patterns of potential ways to run an OS command in an application. Instead of scrutinizing code for exploitable vulnerabilities, the recommendations in this cheat sheet pave a safe road for developers that mitigate the possibility of command injection in your code. By following these recommendations, you can be reasonably sure your code is free of command injection.
Check your project using Semgrep
The following command runs an optimized set of rules for your project:
semgrep --config p/default
1. Running OS commands
1.A. Open3 module
Open3
grants access to running processes when running another program. For more information, see Ruby documentation. Such methods as capture2
, capture2e
, capture3
, popen2
, popen2e
, popen3
, pipeline
, pipeline_r
, pipeline_rw
, pipeline_start
and pipeline_w
are intended for running commands provided as a string. Letting user supplied data in a command that is passed as an argument to one of these methods, can create an opportunity for a command injection vulnerability.
Examples:
require 'open3'
# safe
Open3.popen3("ls -la")
# vulnerable
user_input = " && cat /etc/passwd" # Value supplied by user
Open3.popen3("ls #{user_input}")
require 'open3'
# safe
fname = "/usr/share/man/man1/ls.1.gz"
Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
# vulnerable
user_input = " && cat /etc/passwd" # Value supplied by user
Open3.pipeline("zcat #{user_input}", "nroff -man", "colcrt")
References
Open3
documentation.
Mitigation
-
Do not pass user input to
Open3
methods. -
Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
-
Don't pass user-controlled input or use an allowlist for inputs.
-
Do not include command arguments in a command string, use parameterization instead. For example:
Instead of the following code:
Open3.pipeline(["bash", "-c", "myCommand myArg1 " + input_value])
Use:
Open3.pipeline(["/path/to/myCommand", "myArg1", input_value])
-
Define a list of allowed arguments.
-
Avoid non-literal values for the command string. Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-open3-pipeline.dangerous-open3-pipeline1.B. open() function
The open(...)
function creates an input/output (I/O) object connected to a stream, file, or subprocess. If the first argument starts with a pipe character (|
), it creates a subprocess. An opportunity for a command injection vulnerability is created when the subprocess includes user input in a command argument to open()
function.
Example:
# safe
open("my_file.txt")
# vulnerable
user_input = “|cat /etc/passwd” # Value supplied by user
open(user_input)
References
- open documentation.
Mitigation
- Do not provide raw user input to the
open()
function. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed command arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-open.dangerous-open1.C. system() function
The system()
function executes OS commands in a subshell. This might potentially lead to a command injection vulnerability when used with user input. A malicious actor can potentially run OS commands to exploit the system.
Example:
# safe
system("ls -lah /tmp")
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
system("ls #{user_input}")
References
Mitigation
- Do not provide raw user input to the
system()
function. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-exec.dangerous-exec1.D. exec() function
The exec()
function executes OS commands. This might potentially lead to a command injection vulnerability when used with user input. A malicious actor can potentially run OS commands to exploit the system.
Example:
# safe
exec("ls -lah /tmp")
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
exec("ls #{user_input}")
References
Mitigation
- Do not provide raw user input to the
exec()
function. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-exec.dangerous-exec1.D. spawn() function
The spawn()
function executes OS commands. This might potentially lead to a command injection vulnerability when used with user input. A malicious actor can potentially run OS commands to exploit the system.
Example:
# safe
pid = spawn("ls -lah /tmp")
Process.wait pid
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
pid = spawn("ls #{user_input}")
Process.wait pid
References
Mitigation
- Do not provide raw user input to the
spawn()
function. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-exec.dangerous-exec1.E. Backticks (``) or %x[command] methods
Backticks ``
or %x[command]
methods allow Ruby developers to execute system commands and return their outputs. Both methods accept string interpolation. As for other methods mentioned in this cheat sheet, when this method is used with user input, it can lead to a command injection vulnerability.
Ruby interprets the text inside of backticks as an OS command. For example, `ls -l`
interpreted by Ruby prints the contents of current working directory. In addition, if the %x
is used with various delimiters, it is also interpreted as an OS command. The `ls -l`
in Ruby is equivivalent to the following:
%x`ls -l`
%x;ls -l;
%x(ls -l)
%x"ls -l"
%x{ls -l}
%x:ls -l:
%x'ls -l'
%x[ls -l]
Example:
# safe
`ls -lah /tmp`
%x[ ls -lah /tmp ]
%x{ ls -lah /tmp }
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
`ls #{user_input}`
%x{ls #{user_input}}
References
- Ruby Kernel documentation.
- Ruby command injection documentation.
Mitigation
- Do not provide raw user input to
``
or%x
methods. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-subshell.dangerous-subshell1.F. Process.spawn and Process.exec methods
The spawn
and exec
methods execute a system command and return its output. Both methods accept string interpolation. Similarly to other methods mentioned in this cheat sheet, when either of these methods is used with user input, it can lead to command injection vulnerability.
https://ruby-doc.org/3.2.1/Process.html
Example:
# safe
Process.spawn("ls -alh")
Process.spawn("ls", "-alh")
Process.spawn(["ls", "-alh"])
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
Process.spawn("ls #{user_input}")
# safe
Process.exec("ls -alh")
Process.exec("ls", "-alh")
Process.exec(["ls", "-alh"])
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
Process.exec("ls #{user_input}")
References
- Process documentation.
Mitigation
- Do not provide raw user input to
Process.spawn
andProcess.exec
methods. - Always try to use internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-exec.dangerous-exec1.F. PTY.spawn method
The PTY.spawn
method executes OS commands in a new terminal. This might potentially lead to a command injection vulnerability when used with user input. A malicious actor can potentially run OS commands to exploit the system.
Example:
# safe
stdout,stdin,pid = PTY.spawn("ls -lah")
# vulnerable
user_input = ' && cat /etc/passwd' # Value supplied by user
stdout,stdin,pid = PTY.spawn("ls #{user_input}")
References
- PTY library documentation.
Mitigation
- Do not provide raw user input to
PTY.spawn
methods. - Always try to use the internal Ruby API (if it exists) instead of running an OS command. Use internal language features instead of invoking commands that can be exploited.
- If the use of user input is unavoidable, create an allowlist for inputs, such as allowed arguments.
- Strip everything except alphanumeric characters from an input provided for the command string and arguments.
Semgrep rule
ruby.lang.security.dangerous-exec.dangerous-execNot finding what you need in this doc? Ask questions in our Community Slack group, or see Support for other ways to get help.