Link Search Menu Expand Document

OS Command Injection in Ruby

Vulnerable example

The following snippet contains a Ruby on Rails model that executes the nslookup command to resolve the host supplied by the user.

class Resolver < ActiveRecord::Base
 def self.lookup(hostname)
    system("nslookup #{hostname}")

Since the hostname is simply interpolated into the string command and executed on a subshell, an attacker could stack another command using ; in the GET parameter to inject additional commands. The screenshot shows an attack injecting the cat command to disclose /etc/passwd.



Ruby has a native API to execute commands. Many of the APIs ingest arguments as a list, which shall be always preferred over sending multiple arguments as single strings. This helps to avoid introducing command injection vulnerabilities.

exec([cmdname, argv0], arg1, ...) # Or exec(cmdname, arg1, ...)
system([cmdname, argv0], arg1, ...) # Or system(cmdname, arg1, ...)
IO.popen([env, [cmdname, argv0], arg1, ..., opts]) # Or IO.popen([env, cmdname, argv1, arg1, ..., opts])
Open3.popen3([cmdname, argv0], arg1, ...) {} # Methods popen2 and popen2e also exist
Open3.capture2([cmdname, argv0], arg1, ...) {} # Methods capture2 and capture2e also exits

Passing the command as a single string introduces the vulnerability:

system("nslookup #{hostname}")    # WRONG

Passing the command as a list of arguments is the safer approach that should always be used, but it might be vulnerable to argument injection depending on the binary.

system("nslookup", hostname)

Some methods only accept the command argument as a single string and are prone to introducing an injection vulnerability. These functions should not be used, or at least used very carefully, to escape or filter out the characters against an allow list (e.g. filtering out everything that is not alphanumeric).

`cmd` # Or Kernel.`("cmd")
%x( cmd )