Forming sanitary shell commands or system calls in Ruby

10,585

Solution 1

It doesn't look like you need a shell for what you're doing. See the documentation for system here: http://ruby-doc.org/core/classes/Kernel.html#M001441

You should use the second form of system. Your example above would become:

system 'usermod', '-p', @options['shadow'], @options['username']

A nicer (IMO) way to write this is:

system *%W(usermod -p #{@options['shadow']} #{@options['username']})

The arguments this way are passed directly into the execve call, so you don't have to worry about sneaky shell tricks.

Solution 2

If you need not just the exit status but also the result you probably want to use Open3.popen3:

require 'open3'
stdin, stdout, stderr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets
sterr.gets

More information here: Getting output of system() calls in Ruby

Solution 3

I'd suggest looking into the 'shellwords' module. This script:

require 'shellwords'
parts = ['echo', "'hello world'; !%& some stuff", 'and another argument']
command = Shellwords.shelljoin( parts )
puts command
output = `#{ command }`
puts output

outputs the escaped text and the expected output:

echo \'hello\ world\'\;\ \!\%\&\ some\ stuff and\ another\ argument
'hello world'; !%& some stuff and another argument

Solution 4

This is an old question, but since it's pretty much the only real answer you'll find when googling I thought I'd add a caveat. The multi argument version of system seems reasonably safe on Linux, but it is NOT on Windows.

Try system "dir", "&", "echo", "hi!" on a Windows system. Both dir and echo will be run. Echo could of course just as well be something far less innocuous.

Share:
10,585
arbales
Author by

arbales

Designer/Developer

Updated on June 04, 2022

Comments

  • arbales
    arbales almost 2 years

    I'm building a daemon that will help me manage my server(s). Webmin works fine, as does just opening a shell to the server, but I'd prefer to be able to control server operations from a UI I design, and also expose some functionality to end users.

    The daemon will pick up actions from a queue and execute them. However, since I'll be accepting input from users, I want to make sure they're not permitted to inject something dangerous into a privileged shell command.

    Here's a fragment that exemplifies my problem:

    def perform
      system "usermod -p #{@options['shadow']} #{@options['username']}"
    end
    

    A gist that explains more: https://gist.github.com/773292

    I'm not positive if typical escaping and sanitizing of inputs is enough for this case, and being a designer, I don't have a ton of security-related experience. I know that this is something that should probably be obvious to me, but its not!

    How can I ensure that the web application that will create and serialize the actions can't pass dangerous text into the privileged process that receives the actions?

    Thanks for the help
    arb

  • arbales
    arbales over 13 years
    so, let's say if I exposed this class, completely unfiltered (I won't) to an end user, and they provided a shadow hash and then "username; rm -rf /" as the username — this wouldn't have the effect of obliterating /
  • cam
    cam over 13 years
    Correct. The arguments are passed directly to the executed program. You can verify this by yourself. Try running ruby -e 'system *W(ls -l foo; rm -rf /)'
  • arbales
    arbales over 13 years
    ah, well great. It does make perfect sense. I think I have this thought that ensuring an application is secure must naturally be harder than it is, harder than simple steps and facts, as if there exists a dangerous edge case for everything. This is likely because I've never read / learned very much about it.
  • Stuart Nelson
    Stuart Nelson almost 11 years
    this also has the added benefit of using system calls, instead of making the calls through bash and then to the system (saves a little overhead on top of the sanitization)
  • Daniel Le
    Daniel Le over 2 years
    The example given by above should read ruby -e 'system *%w(ls -l foo; rm -rf /)' instead.