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
    CommentedNov 18, 2024 at 5:43
  • 1
    Easy to test this yourself. bash -c 'echo "$1"' - '$(whoami)'
    – tripleee
    CommentedNov 18, 2024 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)CommentedNov 17, 2024 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.CommentedNov 18, 2024 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
      CommentedNov 18, 2024 at 19:52
    • @ikkachu Thanks! I've updated my answer.CommentedNov 19, 2024 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
      CommentedNov 17, 2024 at 11:24
    • 9
      No it can't. Please give any example it doing so.CommentedNov 17, 2024 at 22:55

    You must log in to answer this question.

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.