5

If $1 contains untrusted user input for example $(whoami). Are any of the following bash examples vulnerable to command injection?

I'm having issues clearly understanding this behavior in Bash. Also, I have issues with echo -n "$1" and echo "$1" as I think both essentially firstly pass the unsafe contents through a double quoted echo "" which could expand the command injection I think.

# Example 1: Unsafe?
# Passed unsafe input directly into double quoted echo "" on printf line 1

printf "some text printed between single quotes '%s'" "$(echo -n "$1" | base64 -w0 )"
# Example 2: Unsafe?
# Passed unsafe input directly into double quots echo "" on sanitized line 1

sanitized=$(echo "$1" | sed 's/[$`\\]//g')
printf "some text printed between single quotes '%s'" "$(echo -n "$sanitized" | base64 -w0 )"
# Example 3: Safe?
# Replaced dangerous characters using sed before being passed into double quoted echo "" 

sanitized=$(sed 's/[$`\\]//g' <<< $1)
printf "some text printed between single quotes '%s'" "$(echo -n "$sanitized" | base64 -w0 )"

Also, how to fix this if the user input is untrusted, while not stripping any characters (like in the second example) but being safe from command injection.

Or, are all of these examples safe from command injection as long as eval is not involved in this? If not vulnerable to command injection, are there other risks in this context I should be aware of when the user input is untrusted?

2
  • 3
    While not vulnerable to command injection, you do have a problem: you're using echo -n instead of printf %s. Test -e as the input.
    – muru
    Commented Nov 18 at 5:43
  • 1
    Easy to test this yourself. bash -c 'echo "$1"' - '$(whoami)'
    – tripleee
    Commented Nov 18 at 15:39

3 Answers 3

11

All are safe (though Esa's approach is nicer)

The result of 'parameter and variable expansion' in bash is NEVER rescanned for (further) expansions, except wordsplitting and 'filename expansion' (better known as globbing) IF UNQUOTED, unless as you note you use eval or you pass it to another shell either directly e.g. echo "$1" | sh or sh -c "echo '$1'" or by using some other program that invokes a shell e.g. any of system(x) print|x x|getline in awk. See the documentation where if you look closely everything from tilde expansion to arithmetic expansion are separated by commas meaning they are done at (conceptually) the same time and qualified by "done ... left-to-right", whereas only braces, wordsplit, and glob are separated by semicolons meaning they are done sequentially.

With braces excluded, this is true for any POSIX shell and even for zsh in its not-quite-POSIX default mode.

In fact for your example even unquoted $1 would be okay barring silly values for IFS, because your value contains no whitespace and no glob-special characters. However, you can't rely on that in general, so "$1" is a good idea.

7

On Bash >= 4.0 you could use shell parameter expansion to remove these special characters:

sanitized="${1//[\$\`\\]/}"

However, it does not help much if the $1 already comes from a Bash command line calling the script, as the $(whoami) has already been executed and the $1 now contains the output rather than the command.

With Bash scripting, you can only prevent your users from doing stupid things unintentionally, but you must remember that they already have shell access. If the input comes from somewhere else, it might be better to revise the idea of using Bash scripting in the first place.

4
  • 1
    Your first point is good, but ./script '$(command)' passes the actual construct not the output to the script (and is well within the sorts of things attackers would do) Commented Nov 17 at 22:54
  • Thanks @dave_thompson_085! I was trying to explain why the two examples might have seemed unsafe if $(command) was passed unquoted (without 's), but your explanation is more clear. Commented Nov 18 at 5:03
  • not like that you can't remove anything, as that leaves the shell looking for the end of the backquote-command substitution. You'll need to escape the backquote too (and you might as well escape the dollar sign too while you're at it). sanitized="${1//[\$`\\]/}" seems to work in Bash 5.1, Bash 3.2 and zsh, at least.
    – ilkkachu
    Commented Nov 18 at 19:52
  • @ikkachu Thanks! I've updated my answer. Commented Nov 19 at 7:30
-4

echo -n "$1" can expand to command injection if $1 contains special characters

2
  • I know hence the question. So, examples 1 and 2 are unsafe but 3 is?
    – Bob Ortiz
    Commented Nov 17 at 11:24
  • 9
    No it can't. Please give any example it doing so. Commented Nov 17 at 22:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .