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.

Creative Commons License
This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.

Related posts:

  1. RVM Proxies for Common Commands? (Solved)
  2. Greenletters: Painless automation and testing for command-line applications
  3. Utility Belt
  4. Ruby Subprocesses Part 3
  5. Everything You Love about Java is Everything I Love About Good Design
This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.