Safely executing commands with user data

Do you ever need to call out to a shell command with some user-supplied arguments?

category = 'riddles'
fortune = `fortune #{category}`
Q:      What's the difference between a Mac and an Etch-a-Sketch?
A:      You don't have to shake the Mac to clear the screen.

It all seems fine and good until some wise-ass comes along and messes things up for everyone.

category = '; rm -rf *'
fortune = `fortune #{category}`

It doesn’t have to be this way though. You can force process arguments to be interpreted strictly as strings instead of being subject to shell evaluation. You just have to use the multi-argument form of methods which start subprocesses. This means that backquotes are out. We can use #system() though…

filename = '; rm -rf *'
system('test', '-e', filename) # => false

That doesn’t help us with our original example, though. We need the output. Open3 will do the trick:

require 'open3'
category = '; rm -rf *'
output = ""
fortune = Open3.popen3('fortune', category) do |stdin, stdout, stderr|
  output += stdout.read
  output += stderr.read
end
puts output
No fortunes found

This technique is not a sure-fire defence against malicious data. It will be of little help if the command you are invoking can start a subshell, so be especially careful when calling out to shells or to other scripting language interpreters. But for simple commands it can provide a layer of protection.

Leave a Reply

Your email address will not be published. Required fields are marked *