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 grep with a negated character class to check for forbidden characters, or case statements 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 $filename contains spaces or characters like ;, they are treated as part of the filename argument to cat, 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 eval command.

  • 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 to eval.

    # Instead of:
    # cmd="echo Hello, $name"
    # eval $cmd
    
    # Do this:
    name="$1"
    echo "Hello, $name"
    
  • Why it works: eval is 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 noglob at 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: noglob prevents 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 echo where the output might be influenced by user-supplied data, or where special escape sequences could be present.

  • Fix: Replace echo with printf.

    #!/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 -0 with 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.

Want structured learning?

Take the full Bash course →