Skip to main content

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

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-pipeline

1.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

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-open

1.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-exec

1.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-exec

1.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-exec

1.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

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-subshell

1.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

Mitigation

  • Do not provide raw user input to Process.spawn and Process.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-exec

1.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-exec