TL;DR
Protect your bash scripts from command injection vulnerabilities by properly quoting arguments and validating input. This guide shows you how.
1. Understanding the Problem
Bash scripts can be vulnerable if they execute untrusted data as commands. This is called command injection. Imagine a script that takes a filename as an argument and then uses it in a command like rm. If someone provides a malicious filename (e.g., file; rm -rf /), they could delete files they shouldn’t.
2. Quoting Arguments
The most important thing is to always quote your arguments, especially when passing them to commands that might interpret special characters. Double quotes are generally the best choice.
- Single Quotes: Prevent all variable expansion and command substitution. Useful for literal strings.
- Double Quotes: Allow variable expansion but still protect spaces and most other special characters. This is usually what you want.
Example:
filename="my file with spaces" # Correctly quoted
Without quotes, the shell will split the filename into multiple arguments.
3. Using printf %q for Safe Argument Passing
The printf %q format specifier is a powerful tool to safely escape arguments for use in commands. It handles spaces, special characters, and even newlines correctly.
Example:
filename="my file with spaces"
safe_filename=$(printf %q "$filename")
echo rm "$safe_filename" # Safe to execute
4. Validating Input
Even with quoting, it’s good practice to validate input whenever possible. This means checking that the argument meets your script’s expectations.
- Check File Existence: If you expect a file, verify it exists and is a regular file.
- Restrict Characters: Allow only specific characters in the argument (e.g., alphanumeric characters).
- Length Limits: Limit the length of the argument to prevent excessively long inputs.
Example (checking if a file exists):
if [ -f "$filename" ]; then
echo "File exists."
else
echo "Error: File does not exist."
fi
5. Avoid eval
The eval command executes strings as commands. It’s extremely dangerous with untrusted input and should be avoided whenever possible.
6. Using Arrays for Multiple Arguments
When dealing with multiple arguments, use arrays to avoid splitting issues.
Example:
arguments=("file1" "file2 with spaces" "file3")
command="rm"
${command} "${arguments[@]}"
7. Parameter Expansion for Default Values
Use parameter expansion to provide default values if an argument is missing.
Example:
filename=${1:-default_file.txt}
echo "Using filename: $filename"
This sets filename to the first command-line argument ($1) if provided; otherwise, it defaults to default_file.txt.

