The eval command is very powerful, but it is also very easy to abuse.
It causes the code to be parsed twice instead of once. This means that if your code contains a variable reference, the shell parser will evaluate the contents of that variable. If the variable contains a shell command, the shell may run the command, whether or not you want it to run. This can lead to unexpected results, especially when variables can be read from untrusted sources such as users or user-created files.
Note that the eval command is widely considered dangerous in programming. It can execute arbitrary shell code, including malicious code, and should be used with caution.
Bash's name reference problem
Bash 4.3 introduces declare -n ("name reference") to mimic the nameref?? feature of Korn shell, allowing variables to hold references to other variables. However, there are some problems with the implementation used in Bash.
First, Bash's declare -n?? actually does not avoid name conflict issues:
$ foo() { declare -n v=$1; } $ bar() { declare -n v=$1; foo v; } $ bar v bash: warning: v: circular name reference
In other words, we cannot assign a safe name to the name reference. If the caller's variable happens to have the same name, it's troublesome.
Second, Bash's name reference implementation still allows arbitrary code execution:
$ foo() { declare -n var=$1; echo "$var"; } $ foo 'x[i=$(date)]' bash: i=Thu Mar 27 16:34:09 EDT 2034: syntax error in expression (error token is "Mar 27 16:34:09 EDT 2023")
This example is not elegant, but you can clearly see that the date?? command is actually executed. This is by no means the result we want.
Despite these drawbacks, the declare -n?? feature is a step in the right direction. But you have to be careful to choose a name that the caller will not use (which means you need some control over the caller, even if you just tell them "don't use variables that start with _my_pkg??") and you have to reject unsafe input.
Good use examples of eval
The most common correct way to use eval is to read variables from program outputs specially designed to be used in this way. For example,
# On the old system, you must run the following command after resizing the window:eval "`resize`" # More advanced usage: Get the password phrase for SSH private key.# This is usually performed from a file of type .xsession or .profile.# The variables generated by the ssh-agent will be exported to all processes in the user session so that the subsequent ssh command can inherit these variables.eval "`ssh-agent -s`"
eval has other uses, especially when creating variables (see indirect variable references ↗). Here is an example of parsing command line options without parameters:
# POSIX # # Dynamically create option variables. Try to call:# # sh -x --verbose --test --debug for i; do case $i in --test|--verbose|--debug) shift # Remove options from the command line name=${i#--} # Remove option prefix eval "$name=\$name" # Create *new* variables ;; esac done echo "verbose: $verbose" echo "test: $test" echo "debug: $debug"
So, why is this version acceptable? This is because we restrict the use of the eval command and will only be executed if the input is one of a limited set of known values. Therefore, the user cannot abuse it to cause arbitrary command execution - any input containing strange content will not match one of the three predetermined possible inputs.
Note that this is still not recommended: it's a very steep road and it's easy for later maintenance to turn this code into dangerous content. For example, you want to add a feature that allows passing a bunch of different --test-xyz options. You change --test to --test-* without much effort checking the implementation of other parts of the script. You test your use case and everything works fine. Unfortunately, you just introduced arbitrary command execution:
$ ./foo --test-'; ls -l /etc/passwd;x=' -rw-r--r-- 1 root root 943 2007-03-28 12:03 /etc/passwd
Again: Allowing the eval command to be used on unfiltered user input will result in arbitrary command execution.
Do everything possible to avoid passing data to eval, even if your code seems to handle all boundary cases.
If you have thought through and looked for an alternative from #bash but haven't found any, skip to the "Robust eval usage" section.
Problems with declare
Can you better accomplish this task using declare?
for i in "$@"; do case "$i" in --test|--verbose|--debug) shift # Remove options from the command line name=${i#--} # Remove option prefix declare $name=Yes # Set default values ;; --test=*|--verbose=*|--debug=*) shift name=${i#--} value=${name#*=} # value is the content after the first word and = name=${name%%=*} # Limited to the first word (even if there is another one in the value =) declare $name="$value" # Create *new* variables ;; esac done
Note that --name is used for the default value, and --name=value is the required format.
Here is a good use example of eval for reading variables from program output specially designed to be used in this way:
# On the old system, you must run the following command after resizing the window:eval "`resize`" # More advanced usage: Get the password phrase for SSH private key.# This is usually performed from a file of type .xsession or .profile.# The variables generated by the ssh-agent will be exported to all processes in the user session so that the subsequent ssh command can inherit these variables.eval "`ssh-agent -s`"
eval can also be used when creating variables, especially when creating indirect variable references. Here is an example of parsing command line options without parameters:
# POSIX # # Dynamically create option variables. Try to call:# # sh -x --verbose --test --debug for i; do case $i in --test|--verbose|--debug) shift # Remove options from the command line name=${i#--} # Remove option prefix eval "$name=\$name" # Create *new* variables ;; esac done echo "verbose: $verbose" echo "test: $test" echo "debug: $debug"
Although the use of eval in this example looks safe, it is still not recommended to use the eval command extensively because it requires very careful input filtering and verification to avoid arbitrary command execution vulnerabilities. Try to avoid passing data to eval and look for alternatives to increase script security.
Problems with using declare
Can't using declare better solve this problem?
for i in "$@"; do case "$i" in --test|--verbose|--debug) shift # Remove options from the command line name=${i#--} # Remove option prefix declare $name=Yes # Set default values ;; --test=*|--verbose=*|--debug=*) shift name=${i#--} value=${name#*=} # The value is the content after the equal sign name=${name%%=*} # Only the name of the first word (even if there is another equal sign in the value) declare $name="$value" # Create *new* variables ;; esac done
Note that by default, --name and --name=value are required formats.
For some inputs, declare does work better:
griffon:~$ name='foo=x;date;x' griffon:~$ declare $name=Yes griffon:~$ echo $foo x;date;x=Yes
But it still causes arbitrary code execution in the array variable:
attoparsec:~$ echo $BASH_VERSION 4.2.24(1)-release attoparsec:~$ danger='( $(printf "%s!\n" DANGER >&2) )' attoparsec:~$ declare safe=${danger} attoparsec:~$ declare -a unsafe attoparsec:~$ declare unsafe=${danger} DANGER!
This code shows the security issues that may be caused by using declare. In some cases, using declare can lead to arbitrary code execution, creating potential security vulnerabilities. In this example, the value of the variable contains a command that will be executed when a variable is declared using declare. This can lead to untrusted code execution, causing security issues.
To ensure the security of the script, untrusted data should be avoided to pass to the declare command. If you need to create variables on the fly, consider using other secure approaches or finding alternatives to avoid potential security risks.
Powerful usage of eval
Almost always (at least 99% or more of the time in Bash, but also for cleaner shells), the correct way to use eval is to generate layers of abstraction hidden behind functions in the library code. This allows the function to have the following functions:
- Presenting a well-defined interface to the caller of a function, specifying which inputs must be strictly controlled by the programmer and which may be unpredictable, such as side effects affected by user input. It is important to record which options and parameters are not safe without control.
- Input validation for certain types of inputs, if possible, such as integers. In this case, it is easy to exit and return an error state that can be handled by the caller of the function.
- Create abstractions that hide the details of ugly implementations using eval.
Generally, eval is correct when at least all of the following conditions are met:
- All possible eval parameters are guaranteed to not cause harmful side effects or lead to the execution of arbitrary code under any circumstances. These inputs are statically encoded, do not interact with uncontrolled dynamic code, and/or are thoroughly verified. This is why functions are important, because you don't necessarily need to guarantee this guarantee yourself. As long as your function records which inputs may be dangerous, you can delegate this task to the caller of the function.
- eval usage presents a clear interface to the user or programmer.
- eval makes it possible to impossible without writing larger, slower, more complex, more dangerous, uglier, less practical code.
If for some reason you still need to build Bash code dynamically and evaluate it, make sure you take the following precautions:
- Always refer to the eval expression: eval 'a=b'
- Always refer to the code in single quotes and extend the data to it using printf's %q: eval "$(printf 'myvar=%q' "$value")"
- Do not use dynamic variable names. This may be exploited even if the %q usage is used.
Why pay attention? If you fail to follow the above recommendations, here are examples where scripts may be exploited:
- If the code is not single quoted, there is a risk of extending the data into it without %q processing. This means that the data is free to be executed:
name='Bob; echo I am arbitrary code'; eval "user=$name"
- Even if the input data is processed as a variable name after %q processing, if there is an illegal variable name in the assignment, Bash will search for the command in PATH:
echo 'echo I am arbitrary code' > /usr/local/bin/a[1]=b; chmod +x /usr/local/bin/a[1]=b; var='a[1]' value=b; eval "$(printf '%q=%q' "$var" "$value")"
This is the end of this article about practical eval commands and security issues in shells. For more related shell eval commands, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!