Bash script injection vulnerabilities arise when untrusted input is incorporated into a Bash script without proper sanitization, allowing an attacker to execute arbitrary commands.
Here’s how to see it in action:
Imagine a simple script that takes a filename as an argument and then cats it:
#!/bin/bash
filename="$1"
echo "Displaying content of: $filename"
cat "$filename"
If an attacker can control the $filename variable, they can inject commands. For example, instead of a filename like my_document.txt, they could provide:
"my_document.txt; rm -rf /"
The script would then execute:
cat "my_document.txt; rm -rf /"
Bash, seeing the semicolon, would interpret this as two separate commands: first cat "my_document.txt", and then rm -rf /.
To prevent this, we need to treat all external input as literal data, not executable code.
The most robust way to handle this is by using shell-specific quoting and escaping mechanisms, or by employing safer alternatives.
1. Strict Input Validation and Whitelisting
This is the gold standard. Instead of trying to remove bad characters, define exactly what characters are allowed.
-
Diagnosis: Review the script and identify all points where external input is used. For each input, ask: "What characters should be in this input?"
-
Fix: Use
grepwith a negated character class to check for forbidden characters, orcasestatements to match allowed patterns.#!/bin/bash filename="$1" # Whitelist allowed characters: alphanumeric, underscore, hyphen, dot if [[ ! "$filename" =~ ^[a-zA-Z0-9_\-\.]+$ ]]; then echo "Error: Invalid filename characters." exit 1 fi echo "Displaying content of: $filename" cat "$filename" -
Why it works: This explicitly denies any input that doesn’t conform to a known safe pattern, preventing any special characters (like
;,|,&,$,\``,(,)`) from being interpreted as commands.
2. Proper Quoting
Always double-quote variables when they are used in commands. This tells the shell to treat the variable’s content as a single string, even if it contains spaces or special characters.
-
Diagnosis: Search your script for variable expansions that are not enclosed in double quotes, especially when they are passed as arguments to commands or used in control structures.
-
Fix: Add double quotes around all variable expansions.
#!/bin/bash filename="$1" # Previous problematic: cat $filename echo "Displaying content of: $filename" cat "$filename" # Corrected -
Why it works:
cat "$filename"ensures that if$filenamecontains spaces or characters like;, they are treated as part of the filename argument tocat, not as separate commands or shell metacharacters.
3. Avoid eval
The eval command is a prime vector for injection because it re-parses and re-executes its arguments as shell commands.
-
Diagnosis: Search your script for any instance of the
evalcommand. -
Fix: Refactor the script to avoid
eval. Often, you can achieve the same result by directly executing commands or using other built-in shell features. If you absolutely must construct a command string and execute it, carefully sanitize every part of the string before passing it toeval.# Instead of: # cmd="echo Hello, $name" # eval $cmd # Do this: name="$1" echo "Hello, $name" -
Why it works:
evalis inherently dangerous because it treats its input as a new command line. By avoiding it, you prevent the shell from re-interpreting potentially malicious input.
4. Use set -o noglob for Filenames
If your script is processing filenames and you don’t want shell globbing (like *, ?, []) to expand unexpectedly, noglob is your friend.
-
Diagnosis: Look for commands that operate on filenames supplied via variables, where unexpected wildcard expansion might occur.
-
Fix: Enable
noglobat the start of the relevant section or the entire script.#!/bin/bash set -o noglob # Disable filename expansion filename="$1" echo "Displaying content of: $filename" cat "$filename" set +o noglob # Re-enable if needed later -
Why it works:
noglobprevents characters like*or?within a variable’s value from being interpreted as wildcard patterns by the shell, ensuring they are treated as literal characters.
5. Use printf instead of echo for Complex Output
While echo is often used, its behavior can be quirky and sometimes exploited (e.g., echo -e with backslash escapes, or echo $(command)). printf is more predictable and safer.
-
Diagnosis: Identify uses of
echowhere the output might be influenced by user-supplied data, or where special escape sequences could be present. -
Fix: Replace
echowithprintf.#!/bin/bash message="$1" # Instead of: echo "$message" printf "%s\n" "$message" -
Why it works:
printf "%s\n" "$message"formats the input string literally, followed by a newline. It does not interpret backslash escapes by default, and it treats the entire argument as data to be printed, not as commands or formatting directives that could be manipulated.
6. Sanitize or Escape Special Characters Explicitly
If you cannot perfectly whitelist, you can attempt to escape or remove known dangerous characters. This is generally less secure than whitelisting but can be a necessary intermediate step.
-
Diagnosis: Identify specific metacharacters that could cause injection (
$,\,',",(space),;,|,&,(,),<,>,*,?,~,#,=). -
Fix: Use parameter expansion to replace dangerous characters.
#!/bin/bash input="$1" # Remove potentially dangerous characters. This is a simplified example. # A more comprehensive approach would be needed for full security. sanitized_input=$(echo "$input" | sed 's/[;&|`$()<>*?~#=\\]/\\&/g') echo "Processing: $sanitized_input" # Use the sanitized input in a command # For example: command --option="$sanitized_input" -
Why it works: By prepending a backslash (
\) to known metacharacters, you instruct the shell to treat them as literal characters rather than special operators.
7. Consider Safer Alternatives
For tasks involving parsing or executing code based on external input, consider languages or tools designed for safer handling of such data.
- Diagnosis: If your script is becoming overly complex with sanitization logic, or if you’re dealing with highly sensitive operations based on external input, re-evaluate the tool choice.
- Fix: For string manipulation, Python or Perl might offer more robust and safer methods. For executing external programs with arguments, consider using
xargs -0with null-delimited input, or constructing argument arrays directly if the execution environment supports it. - Why it works: These languages and tools often have built-in mechanisms or established libraries for safely handling untrusted input, reducing the burden on the script author to manually escape every possibility.
The next error you’ll likely encounter after fixing injection vulnerabilities is related to incorrect file permissions or non-existent files, as the script will now strictly adhere to the expected input format.