Differences between revisions 1 and 24 (spanning 23 versions)
Revision 1 as of 2007-05-02 23:39:37
Size: 3576
Editor: redondos
Comment:
Revision 24 as of 2013-07-18 15:19:30
Size: 9485
Editor: Lhunath
Comment: get rid of the cruddy "robust eval" section and add real eval robusting.
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
[[Anchor(faq48)]]
== Why should I never use eval? ==

"eval" is a common misspelling of "evil". The section dealing with spaces in file names used to include the following
quote "helpful tool (which is probably not as safe as the \0 technique)", end quote.

{{{
    Syntax : nasty_find_all [path] [command] <maxdepth>
}}}

{{{
    #This code is evil and must never be used
<<Anchor(faq48)>>
== Eval command and security issues ==
The `eval` command is extremely powerful and extremely easy to abuse.

It causes your code to be parsed twice instead of once; this means that, for example, if your code has variable references in it, the shell's parser will evaluate the contents of that variable. If the variable contains a shell command, the shell might run that command, whether you wanted it to or not. This can lead to unexpected results, especially when variables can be read from untrusted sources (like users or user-created files).

=== Examples of bad use of eval ===

"eval" is a common misspelling of "evil". The section of this FAQ dealing with [[BashFAQ/020|spaces in file names]] used to include the following quote "helpful tool (which is probably not as safe as the \0 technique)", end quote.

{{{
    Syntax : nasty_find_all <path> <command> [maxdepth]
}}}

{{{
    # This code is evil and must never be used!
Line 16: Line 20:
    #warning, evilness     # warning, BAD code
Line 25: Line 29:
This script is supposed to recursively search for files with newlines and/or spaces in them, arguing that {{{find -print0 | xargs -0}}} was unsuitable for some purposes such as multiple commands. It was followed by an instructional description on all the lines involved, which we'll skip. 

To its defense, it works:
This script was supposed to recursively search for files and run a user-specified command on them, even if they had newlines and/or spaces in their names. The author thought that {{{find -print0 | xargs -0}}} was unsuitable for some purposes such as multiple commands.  It was followed by an instructional description of all the lines involved, which we'll skip.

To its defense, it worked:
Line 42: Line 46:
$ 
}}}

But consider this: 
$
}}}

But consider this:
Line 57: Line 61:
Which becomes the two statements {{{ FILES=(""); }}} and {{{ ls -l / }}}. Congratulations, you just allowed execution of arbitrary commands.  Which becomes the two statements {{{ FILES=(""); }}} and {{{ ls -l / }}}. Congratulations, you just allowed execution of arbitrary commands.
Line 87: Line 91:
It doesn't take much imagination to replace {{{ ls -l }}} with {{{ rm -rf }}} or worse.

One might think these circumstances are obscure, but one should not be tricked by this. All it takes is one malicious user, or perhaps more likely, a benign user who left the terminal unlocked when going to the bathroom, wrote a funny php uploading script that doesn't sanity check file names or who made the same mistake as oneself in allowing arbitrary code execution (now instead of being limited to the www-user, an attacker can use {{{nasty_find_all}}} to traverse chroot jails and/or gain additional privileges), uses an IRC or IM client that's too liberal in the filenames it accepts for file transfers or conversation logs, etc.
It doesn't take much imagination to replace {{{ ls -l }}} with {{{ rm -rf }}} or worse.

One might think these circumstances are obscure, but one should not be tricked by this. All it takes is one malicious user, or perhaps more likely, a benign user who left the terminal unlocked when going to the bathroom, or wrote a funny PHP uploading script that doesn't sanity check file names, or who made the same mistake as oneself in allowing arbitrary code execution (now instead of being limited to the www-user, an attacker can use {{{nasty_find_all}}} to traverse chroot jails and/or gain additional privileges), or uses an IRC or IM client that's too liberal in the filenames it accepts for file transfers or conversation logs, etc.

=== Examples of good use of eval ===

The most common correct use of `eval` is reading variables from the output of a program which is '''specifically ''designed'' to be used this way'''. For example,

{{{
# On older systems, one must run this after resizing a window:
eval "`resize`"

# Less primitive: get a passphrase for an SSH private key.
# This is typically executed from a .xsession or .profile type of file.
# The variables produced by ssh-agent will be exported to all the processes in
# the user's session, so that an eventual ssh will inherit them.
eval "`ssh-agent -s`"
}}}

`eval` has other uses especially when creating variables out of the blue ([[BashFAQ/006|indirect variable references]]). Here is an example of one way to parse command line options that do not take parameters:

{{{
# POSIX
#
# Create option variables dynamically. Try call:
#
# sh -x example.sh --verbose --test --debug

for i in "$@"
do
    case "$i" in
       --test|--verbose|--debug)
            shift # Remove option from command line
            name=${i#--} # Delete option prefix
            eval "$name='$name'" # make *new* variable
            ;;
    esac
done

echo "verbose: $verbose"
echo "test: $test"
echo "debug: $debug"
}}}

So, why is this version acceptable? It's acceptable because we have restricted the `eval` command so that it will '''only''' be executed when the input is one of a finite set of known values. Therefore, it can't ever be abused by the user to cause arbitrary command execution -- any input with funny stuff in it wouldn't match one of the three predetermined possible inputs.

Note that this is '''still frowned upon''': It is a slippery slope and some later maintenance can easily turn this code into something dangerous. Eg. You want to ''add a feature'' that allows a bunch of different --test-xyz's to be passed. You change `--test` to `--test-*`, without going through the trouble of checking the implementation of the rest of the script. You test your use case case and it all works. Unfortunately, '''you've 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
}}}

Once again: by permitting the `eval` command to be used on unfiltered user input, we've permitted arbitrary command execution.

'''AVOID PASSING DATA TO EVAL AT ALL COST''', even if your code seems to handle all the edge cases today.

If you have thought really hard and asked #bash for an alternative way but there isn't any, skip ahead to "Robust eval usage".

=== Alternatives to eval ===
 ''Could this not be done better with `declare`? eg:''
 {{{
 for i in "$@"
 do
    case "$i" in
       --test|--verbose|--debug)
            shift # Remove option from command line
            name=${i#--} # Delete option prefix
            declare $name=Yes # set default value
            ;;
       --test=*|--verbose=*|--debug=*)
            shift
            name=${i#--}
            value=${name#*=} # value is whatever's after first word and =
            name=${name%%=*} # restrict name to first word only (even if there's another = in the value)
            declare $name="$value" # make *new* variable
            ;;
    esac
 done
 }}}
 ''Note that `--name` for a default, and `--name=value` are the required formats.''

 `declare` does seem to have some sort of parser magic in it, much like `[[` does. Here's a test I performed with bash 3.1.17:
 {{{
 griffon:~$ declare foo=x;date;x=Yes
 Sun Nov 4 09:36:08 EST 2007
 
 griffon:~$ name='foo=x;date;x'
 griffon:~$ declare $name=Yes
 griffon:~$ echo $foo
 x;date;x=Yes
 }}}
 It appears that, at least in bash, `declare` is '''much''' safer than `eval`.



 {{{
 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!
 attoparsec:~$
 }}}
 Regular variables may be safe with declare, but array variables are not.


For a list of ways to reference or to populate variables indirectly without using `eval`, please see [[BashFAQ/006|FAQ #6]]. (This section was written before #6 was, but I've left it here as a reference.)

=== Robust eval usage ===

To make `eval` safe, you need to turn your literal data into bash literals. The easiest way to do this is by using `printf`'s `%q`:

{{{
eval "$(printf '%q=$value' "$var")"
}}}

This turns the data in `var` into a bash literal, meaning it can no longer be used for anything that is not literal (such as running arbitrary code). It is now safe for injection into, in this case, an assignment. NOTE: `$value` is not made literal by %q, but IT IS SINGLE QUOTED. You should ALWAYS single-quote the format string to printf: we don't want to allow `$value` to expand at this time: we want to end up with a string: `varcontent=$value`, which `eval` will then evaluate. If you're sloppy and double-quote the printf format string, you've allowed arbitrary code execution through `value`'s content!

----
CategoryShell

Eval command and security issues

The eval command is extremely powerful and extremely easy to abuse.

It causes your code to be parsed twice instead of once; this means that, for example, if your code has variable references in it, the shell's parser will evaluate the contents of that variable. If the variable contains a shell command, the shell might run that command, whether you wanted it to or not. This can lead to unexpected results, especially when variables can be read from untrusted sources (like users or user-created files).

Examples of bad use of eval

"eval" is a common misspelling of "evil". The section of this FAQ dealing with spaces in file names used to include the following quote "helpful tool (which is probably not as safe as the \0 technique)", end quote.

    Syntax : nasty_find_all <path> <command> [maxdepth]

    # This code is evil and must never be used!
    export IFS=" "
    [ -z "$3" ] && set -- "$1" "$2" 1
    FILES=`find "$1" -maxdepth "$3" -type f -printf "\"%p\" "`
    # warning, BAD code
    eval FILES=($FILES)
    for ((I=0; I < ${#FILES[@]}; I++))
    do
        eval "$2 \"${FILES[I]}\""
    done
    unset IFS

This script was supposed to recursively search for files and run a user-specified command on them, even if they had newlines and/or spaces in their names. The author thought that find -print0 | xargs -0 was unsuitable for some purposes such as multiple commands. It was followed by an instructional description of all the lines involved, which we'll skip.

To its defense, it worked:

$ ls -lR
.:
total 8
drwxr-xr-x  2 vidar users 4096 Nov 12 21:51 dir with spaces
-rwxr-xr-x  1 vidar users  248 Nov 12 21:50 nasty_find_all

./dir with spaces:
total 0
-rw-r--r--  1 vidar users 0 Nov 12 21:51 file?with newlines
$ ./nasty_find_all . echo 3
./nasty_find_all
./dir with spaces/file
with newlines
$

But consider this:

$ touch "\"); ls -l $'\x2F'; #"

You just created a file called  "); ls -l $'\x2F'; #

Now FILES will contain  ""); ls -l $'\x2F'; #. When we do eval FILES=($FILES), it becomes

FILES=(""); ls -l $'\x2F'; #"

Which becomes the two statements  FILES=("");  and  ls -l / . Congratulations, you just allowed execution of arbitrary commands.

$ touch "\"); ls -l $'\x2F'; #"
$ ./nasty_find_all . echo 3
total 1052
-rw-r--r--   1 root root 1018530 Apr  6  2005 System.map
drwxr-xr-x   2 root root    4096 Oct 26 22:05 bin
drwxr-xr-x   3 root root    4096 Oct 26 22:05 boot
drwxr-xr-x  17 root root   29500 Nov 12 20:52 dev
drwxr-xr-x  68 root root    4096 Nov 12 20:54 etc
drwxr-xr-x   9 root root    4096 Oct  5 11:37 home
drwxr-xr-x  10 root root    4096 Oct 26 22:05 lib
drwxr-xr-x   2 root root    4096 Nov  4 00:14 lost+found
drwxr-xr-x   6 root root    4096 Nov  4 18:22 mnt
drwxr-xr-x  11 root root    4096 Oct 26 22:05 opt
dr-xr-xr-x  82 root root       0 Nov  4 00:41 proc
drwx------  26 root root    4096 Oct 26 22:05 root
drwxr-xr-x   2 root root    4096 Nov  4 00:34 sbin
drwxr-xr-x   9 root root       0 Nov  4 00:41 sys
drwxrwxrwt   8 root root    4096 Nov 12 21:55 tmp
drwxr-xr-x  15 root root    4096 Oct 26 22:05 usr
drwxr-xr-x  13 root root    4096 Oct 26 22:05 var
./nasty_find_all
./dir with spaces/file
with newlines
./
$

It doesn't take much imagination to replace  ls -l  with  rm -rf  or worse.

One might think these circumstances are obscure, but one should not be tricked by this. All it takes is one malicious user, or perhaps more likely, a benign user who left the terminal unlocked when going to the bathroom, or wrote a funny PHP uploading script that doesn't sanity check file names, or who made the same mistake as oneself in allowing arbitrary code execution (now instead of being limited to the www-user, an attacker can use nasty_find_all to traverse chroot jails and/or gain additional privileges), or uses an IRC or IM client that's too liberal in the filenames it accepts for file transfers or conversation logs, etc.

Examples of good use of eval

The most common correct use of eval is reading variables from the output of a program which is specifically designed to be used this way. For example,

# On older systems, one must run this after resizing a window:
eval "`resize`"

# Less primitive: get a passphrase for an SSH private key.
# This is typically executed from a .xsession or .profile type of file.
# The variables produced by ssh-agent will be exported to all the processes in
# the user's session, so that an eventual ssh will inherit them.
eval "`ssh-agent -s`"

eval has other uses especially when creating variables out of the blue (indirect variable references). Here is an example of one way to parse command line options that do not take parameters:

# POSIX
#
# Create option variables dynamically. Try call:
#
#    sh -x example.sh --verbose --test --debug

for i in "$@"
do
    case "$i" in
       --test|--verbose|--debug)
            shift                   # Remove option from command line
            name=${i#--}            # Delete option prefix
            eval "$name='$name'"    # make *new* variable
            ;;
    esac
done

echo "verbose: $verbose"
echo "test: $test"
echo "debug: $debug"

So, why is this version acceptable? It's acceptable because we have restricted the eval command so that it will only be executed when the input is one of a finite set of known values. Therefore, it can't ever be abused by the user to cause arbitrary command execution -- any input with funny stuff in it wouldn't match one of the three predetermined possible inputs.

Note that this is still frowned upon: It is a slippery slope and some later maintenance can easily turn this code into something dangerous. Eg. You want to add a feature that allows a bunch of different --test-xyz's to be passed. You change --test to --test-*, without going through the trouble of checking the implementation of the rest of the script. You test your use case case and it all works. Unfortunately, you've 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

Once again: by permitting the eval command to be used on unfiltered user input, we've permitted arbitrary command execution.

AVOID PASSING DATA TO EVAL AT ALL COST, even if your code seems to handle all the edge cases today.

If you have thought really hard and asked #bash for an alternative way but there isn't any, skip ahead to "Robust eval usage".

Alternatives to eval

  • Could this not be done better with declare? eg:

     for i in "$@"
     do
        case "$i" in
           --test|--verbose|--debug)
                shift                   # Remove option from command line
                name=${i#--}            # Delete option prefix
                declare $name=Yes       # set default value
                ;;
           --test=*|--verbose=*|--debug=*)
                shift
                name=${i#--}
                value=${name#*=}        # value is whatever's after first word and =
                name=${name%%=*}        # restrict name to first word only (even if there's another = in the value)
                declare $name="$value"  # make *new* variable
                ;;
        esac
     done

    Note that --name for a default, and --name=value are the required formats.

    declare does seem to have some sort of parser magic in it, much like [[ does. Here's a test I performed with bash 3.1.17:

     griffon:~$ declare foo=x;date;x=Yes
     Sun Nov  4 09:36:08 EST 2007
     
     griffon:~$ name='foo=x;date;x'
     griffon:~$ declare $name=Yes
     griffon:~$ echo $foo
     x;date;x=Yes

    It appears that, at least in bash, declare is much safer than eval.

     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!
     attoparsec:~$ 
    Regular variables may be safe with declare, but array variables are not.

For a list of ways to reference or to populate variables indirectly without using eval, please see FAQ #6. (This section was written before #6 was, but I've left it here as a reference.)

Robust eval usage

To make eval safe, you need to turn your literal data into bash literals. The easiest way to do this is by using printf's %q:

eval "$(printf '%q=$value' "$var")"

This turns the data in var into a bash literal, meaning it can no longer be used for anything that is not literal (such as running arbitrary code). It is now safe for injection into, in this case, an assignment. NOTE: $value is not made literal by %q, but IT IS SINGLE QUOTED. You should ALWAYS single-quote the format string to printf: we don't want to allow $value to expand at this time: we want to end up with a string: varcontent=$value, which eval will then evaluate. If you're sloppy and double-quote the printf format string, you've allowed arbitrary code execution through value's content!


CategoryShell

BashFAQ/048 (last edited 2024-03-28 15:00:50 by emanuele6)