/bin/bash - Proper Whitespace Handling - Whitespace Safety - End-of-Options Parameter Security

From Kicksecure
< Dev
Jump to navigation Jump to search

Supporting multiple command line parameters with spaces in wrapper scripts and End-of-Options Parameter (--) for better security.

Safe ways to print[edit]

There is no safe usage of echo, use printf '%s' instead.

shellcheck bug reports:

Please note that printf does not have a default format specifier, but treats the first positional parameter as the format. When the format is missing, the data is treated as if the format specifier is %b. It is always recommended to be explicit on the format being used to avoid this mistake.

Normally, there is no need to interpret the escape sequences of a variable, therefore use the printf format specifier %s when the data is not printed to the terminal:

Click = Copy Copied to clipboard! var="$(printf '%s' "${untrusted_text}")"

If you require escapes to be interpreted, interpret them on a per-need basis:

Click = Copy Copied to clipboard! red="$(printf '%b' "\e[31m")" # red=$'\e[31m' # printf -v red '%b' "\e[31m" nocolor="$(printf '%b' "\e[m")" # nocolor=$'\e[m' # printf -v nocolor '%b' "\e[m"

Escapes that are already interpreted can be printed with %s without making a difference:

Click = Copy Copied to clipboard! var="$(printf '%s' "${red} ${untrusted_text} ${nocolor}")"

And this is why you should use stprint when printing to the terminal, as it will sanitize unsafe characters while simply using printf '%s' is not safe when escapes are already interpreted:

Click = Copy Copied to clipboard! stprint "${red} ${untrusted_text} ${nocolor}" printf '%s' "${red} ${untrusted_text} ${nocolor}" | stprint printf '%s' "${red} ${untrusted_text} ${nocolor}" | stprint | less -R

Rule of thumb:

  • echo: Never!
  • printf: Whenever the printed data is not used by a terminal.
    • Format %b: Only for trusted data
    • Format %s: With any data
  • stprint: Whenever the printed data is used by a terminal.

Resources:

Bash Proper Whitespace Handling[edit]

  • Quote variables
  • Build parameters using arrays
  • Enforce nounset
#!/bin/bash

## https://yakking.branchable.com/posts/whitespace-safety/

set -eu

app_user=user
lib_dir="/tmp/test/lib/program with space/something spacy"
main_app_dir="/tmp/test/home/user/folder with space/abc"
mkdir -p "$lib_dir"
mkdir -p "$main_app_dir"

declare -a cmd

cmd+=("cp")
cmd+=("-r")
cmd+=("${lib_dir}")
cmd+=("${main_app_dir}/")

"${cmd[@]}"

Why nounset[edit]

Because it is better to be explicit if a variable should be empty or not:

rm -f "/$UNSET_VAR"

Will return:

rm: cannot remove '/': Is a directory

Setting UNSET_VAR="" would not fix this issue, but that is another problem, checking if every used variable can be empty or not.

POSIX array[edit]

On a POSIX shell, there is one array, the $@, which have different scopes by function or main script. You can build it with set --:

Add items to an array:

set -- a b c

Add items to the beginning or end of the array:

set -- b
set -- a "$@" c

Use of End-of-Options Parameter (--)[edit]

The end-of-options parameter "--" is crucial because otherwise inputs might be mistaken for command options. This might even be a security risk. Here are examples using the `sponge` command:

Click = Copy Copied to clipboard! sponge -a testfilename </dev/null

Result: OK. This works because "testfilename" doesn't look like an option.

Click = Copy Copied to clipboard! sponge -a --testfilename </dev/null

Result: Fail. The command interprets "--testfilename" as a series of options:

sponge: invalid option -- '-'
sponge: invalid option -- 't'
sponge: invalid option -- 'e'
...

Click = Copy Copied to clipboard! sponge -a -- --testfilename </dev/null

Result: OK. The `--` signals that "--testfilename" is a filename, not an option.

Conclusion:

  • The "--" parameter marks the end of command options.
  • Use "--" at the end of a command to prevent misinterpretation.
  • This technique is applicable to many Unix/Linux commands, not just sponge.

nounset - Check if Variable Exists[edit]

#!/bin/bash

set -x
set -e
set -o nounset

## Enable for testing.
#unset HOME

if [ -z "${HOME+x}" ]; then
    printf '%s\n' "Error: HOME is not set." >&2
fi

printf '%s' "$HOME"

Safely Using Find with End-Of-Options[edit]

Example:

Note: Variable could be different. Could be for example --/usr.

Click = Copy Copied to clipboard! folder_name="/usr"

Click = Copy Copied to clipboard! printf '%s' "${folder_name}" | find -files0-from - -perm /u=s,g=s -print0

Of if safe_echo_nonewline is available from helper-scripts.

Click = Copy Copied to clipboard! # shellcheck disable=SC1091 source /usr/libexec/helper-scripts/safe_echo.sh safe_echo_nonewline "${folder_name}" | find -files0-from - -perm /u=s,g=s -print0

misc[edit]

base_name="${file_name##*/}"
file_extension="${base_name##*.}"

coding style[edit]

  • use:
    • shellcheck
    • safe-rm
    • scurl
    • str_replace
    • append-once
  • use ${variable} style
  • use shell options
set -o errexit
set -o nounset
set -o errtrace
set -o pipefail
  • do not use:
    • which, use command -v instead. This is because which is an external binary (minor reason) and produces stdout if a binary was found, which can be slightly confusing (major reason).

See Also[edit]

Notification image

We believe security software like Kicksecure needs to remain Open Source and independent. Would you help sustain and grow the project? Learn more about our 12 year success story and maybe DONATE!