SafeShell

 4   Tweet
!

Note: This post hasn't been updated in over 2 years.

Need to call out to shell commands to process user-submitted files from your Rails app? You should be using our safe_shell gem.

The Problem

Let’s say a friendly user has uploaded a file called “avatar.jpg”, and you’re using ImageMagick to find out about it. In your app you do:

info = `identify #{filename}`

That'll expand to:

info = `identify avatar.jpg`

All good, right?

Now a malicious user comes along and uploads a file called ";rm -rf .". Now your command expands to:

info = `identify;rm -rf .`

Uh oh. Because the backtick operator forks a shell, and the shell parses the command, this will happily do exactly what you don't want it to. Bye bye anything your in your Rails app that can be deleted.

So what's the answer?

How Not To Fix It

Well, you could try escaping the filename before you put it in, but the various methods of escaping available in Ruby each have their own problems. Besides, why escape the string, then fork a shell to immediately unescape it again?

You could use exec instead of backticks. That doesn't fork a shell, and lets you simply pass the arguments to the command you want to run as method arguments:

exec("identify", filename)

Problem is, that doesn't return the output of the command, which is what you were after in the first place.

SafeShell To The Rescue

Enter SafeShell, riding a gleaming GitHub-shaped steed, and encased in a precious gem:

info = SafeShell.execute("identify", filename)

This calls the given shell command directly, the same way exec does, but returns you the resulting output. No mess, no fuss.

It's got a few other little tricks up its sleave, but you can read about those on GitHub.

  • markus

    What if I want ruby to return immediately leaving the shell command running in the background? The unsafe way would be to append & like this: system(“convert … #{filename} &”).
    Is that possible with SafeShell?

    • cam

      instead of hacky solutions like backgrounding the command, just fork off and exec the command. (i’m assuming you don’t need the output of the command if you want to return immediately. otherwise threading would be your best bet)

  • markus

    I thought that forking a large ruby process would be slow but fork+exec seems to be much faster than back ticks or system(). Here is a mini benchmark from the rails console:

    > Benchmark.bmbm do |x|
    > x.report(‘system’) { 10.times { `ls > /dev/null &` } }
    > x.report(‘fork’) { 10.times { fork{exec(‘ls > /dev/null &’)} } }
    > end
    Rehearsal ——————————————
    system 0.010000 0.040000 0.090000 ( 0.073507)
    fork 0.000000 0.010000 0.010000 ( 0.042714)
    ——————————— total: 0.100000sec

    user system total real
    system 0.010000 0.040000 0.090000 ( 0.070710)
    fork 0.000000 0.010000 0.010000 ( 0.034001)

  • http://attilaolah.eu Attila Oláh

    Using a filename like “$(rm -rf ~)” is better than “;”, as the shell would also execute it if they put quotes around the filename, while that would prevent an attack using “;”.