9

Many answers and comments on this site mention that one should generally avoid using eval in shell scripts. Often these comments are addressed to beginners.

I've seen mention of security issues and robustness, but very few examples.

I already understand what the eval command is, and how to use it. However, the answers given there only mention briefly the dangers of using eval. Remarkably, every answer notes that eval can be a major security hole, but most only mention this fact as a concluding sentence with no elaboration. This seems worthy of further consideration and is really its own question.

Why is using eval a bad idea?

When would it be appropriate to use?

Are there any guidelines by which it can be used with complete and total safety, or is it always and inevitably a security hole?


EDIT: This article is the sort of canonical reference (answer) I was actually looking for when I posted this question. Anyone abusing eval can be pointed to this reference.

Wildcard
  • 36,499
  • If someone manages to sneak in unfiltered user input to an eval'd string… – muru Apr 22 '16 at 20:56
  • See also bash eval builtin command and the links there... – don_crissti Apr 22 '16 at 22:44
  • 1
    @cuonglm, I think we need a canonical "Why not use eval?" that we can point to and which explains the potential security holes when using this command. The question you've linked is in my opinion not the same at all, any more than "How can I list the files in a directory?" is the same as "Why shouldn't I parse the output of ls?" – Wildcard Apr 23 '16 at 03:47
  • @Wildcard: But many answer in that question also mention the security issues. Maybe we should make that question title changed? – cuonglm Apr 23 '16 at 03:48
  • @cuonglm, it's mentioned in passing very briefly, in most cases. I think changing the name would invalidate the answers given, which mostly don't really address the security issues in depth. – Wildcard Apr 23 '16 at 03:53
  • @cuonglm, judging by the enormous number of extremely detailed answers to this question, I would probably get more in-depth answers here if I were insisting loudly that there is nothing wrong with eval and it can and should be conveniently used to parse all user input. ;) Even though that question started out as a rant, it is now the link to provide as to "why not parse ls?" – Wildcard Apr 23 '16 at 03:56

3 Answers3

11

The question will attract opinions...

eval is a part of the standard shell:

The eval utility shall construct a command by concatenating arguments together, separating each with a <space> character. The constructed command shall be read and executed by the shell.

The likely reasons for not liking eval:

  • scripters may not get quoting right, causing unintended behavior (think of the possibilities with unmatched quotes coming from a variable in the evaluation).
  • the result of the evaluation may not be obvious (partly from quoting problems, but partly because eval allows you to set variables based on other variables' names).

On the other hand, if the data going into eval is checked, e.g., the results from testing filenames and ensuring there are no quotes to complicate things, then it is a useful tool. The alternatives usually suggested are less portable, e.g., specific to some particular shell implementation.

Further reading (but bear in mind that bash offers implementation-specific/nonstandard alternatives):

Thomas Dickey
  • 76,765
10

Questions

When would it be appropriate to use?

When the arguments to eval are properly quoted (two levels) and the values of variables expanded by eval in first parsing would not become executed commands in the second parsing.

Why is using eval a bad idea?

It is not IMO (and this question will call for opinions) if you really know what you are doing.

First: check that (the eval of) what you wrote is what you expect by replacing eval by echo and Second: think hard of which variables are locally set and what values they may have.

Are there any guidelines by which it can be used with complete and total safety, or is it always and inevitably a security hole?

There are some conditions in which it could be used in complete and total safety, yes.

But it is always a risk (like it is a risk to write an incorrect script).
It just happens that eval raises the risk to some exponent.

This sure will need a long description:

Details:

The command eval (always a builtin) allows to parse a command-line twice.

It is not more or less dangerous than any other command (think of rm -rf /) in principle. It will be executed with the present user permissions, so think twice before using it while being root. But that is also true of the rm command shown above. For a limited user rm will fail on most directories owned by root. But rm is still a dangerous command.

A known idiom.

The command eval is very useful (and perfectly safe) in known idioms.
For example, this expression prints the value of the last positional parameter:

$ set -- "ls -l" "*" "c;date"

$ eval echo \"\$\{$#\}\"          ### command under test

c;date

The command is un-conditionally safe (as it is) because the only possible result from $# is a number. And the only possible result of $n (with n being a number) is the contents of such positional variable.

If you want to see what the command does, replace eval by echo

$ set -- "ls -l" "*" "c; date"
$ echo echo \"\$\{$#\}\"          ### command under test
echo "${3}"

And echo ${3} is very common (and safe) idiom.

We can still change some of the quoting and get the same result:

$ echo echo '"${'$#\}\"
echo "${3}"
$ eval echo '"${'$#\}\"
c;date

I hope that it is easy for you to see that this new way of quoting is somewhat more obscure that the one above.

An slightly different idiom.

$ b=book
$ book="A Tale of Two Cities"
$ eval 'a=$'"$b"               ### safe for some values of $b.
$ echo "$a"
A Tale of Two Cities

And here we find the first (of two) and main problem with eval:
insufficient quoting:

$ b='book;date'

$ eval 'a=$'"$b"               ### safe for some values of $b.
Fri Apr 22 22:03:09 UTC 2016

The command date got executed (which we didn't intend to).

But this will not execute date (or so I thought thanks @Wildcard).
A correct solution to this command is to sanitize the input, but I will delay talking about this until the second problem of eval has been defined.

$ eval 'a="$'"$b"\"
$ echo "$a"
A Tale of Two Cities;date

And the trick is not difficult, just replace eval by echo and evaluate if the printed command-line is safe. Compare the insecure and secure command:

$ echo 'a=$'"$b"
a=$book;date
$ echo 'a="$'"$b"\"
a="$book;date"

Those simple double quotes will keep the string as an string and will not be converted to a command to execute by the shell.

It may be easier to understand the logic of quoting is we place "external quotes" and "internal quotes" (spaces added for legibility):

$ echo a\=\"\$    "$b"   \"

The internal quotes are the ones around $b which are always a "good idea".
The external ones are all those in backslash (in this example).
The command without spaces (as should be used):

$ echo a\=\"\$"$b"\"
a="$book;date"

$ eval a\=\"\$"$b"\"
$ echo "$a"
A Tale of Two Cities;date

But even this example was very quickly broken by a user (Thanks @Wildcard):

$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Fri Apr 22 23:25:43 UTC 2016

The command executed by eval was thwarted to become malicious, lets use echo:

$ echo a\=\"\$"$b"\"
a="$";date;:""

Thinking of what is the result of quoting twice is never simple.

Second problem.

And this is even more convoluted than the first. In some cases we do not want the result of the first expansion of parameters of eval to remain quoted. In many cases because we want to execute a command inside a variable.

Or, an attacker (as already shown above), crafts an string that match the qutoes and then places a command outside the quotes. The command will get executed and (if we are lucky) a bug will be reported.

That breaks the first layer of protection: quoting
And leaves the values of variables completely open to execution.

In this case, it is imperative that we are sure of the values that a variable will have. All of the possible values.

If a variable value is controlled by some user, we are telling that user:

Give me any command, I will execute it with my permissions.

That is always a dangerous bet, a sure bug, and as root: an crazy action.

Sanitizing external data.

Having said all the above, this is perfectly safe:

#!/bin/bash

a=${1//[^0-9]}       ### Only leave a number (one or many digits).

eval echo $(( a + 1 ))

No matter what an external user place in positional parameter, it will become a number, it may be a big number for which $(( ... )) will fail, but that input will not trigger the execution of any command.

It is now that we can talk about sanitizing variable b from the command above. The command was:

$ b='";date;:"'
$ eval a\=\"\$"$b"\"
Sat Apr 23 01:56:30 UTC 2016

Because b contained several characters that were used to change the line.

$ echo a\=\"\$"$b"\"
a="$";date;:""

Changing to single quotes will not work, some other value of b could match them also. We need to "sanitize" b by removing (at least) the quotes.
We could replace $b by ${b//\"}:

$ eval a\=\"\$"${b//\"}"\"
$ echo "$a"
$;date;:

A more robust variable name sanitizing.

But we can make it even better by acknowledging that a variable name in shell could only have 0-9a-zA_Z and underscores. We can remove all others with this:

$ c="$( LC_COLLATE=C; echo "${b//[^0-9A-Za-z_]}" )"

This will clean the variable b to a valid variable name.
And then, we can add an even more strict check by actually checking that the variable name inside $b (using the cleaned c) exists:

$ if declare -p "$c" &>/dev/null; then   eval a\=\"\$"$c"\" ; fi
  • I like the idiom for printing the last positional parameter; that could be handy! Regarding the line for "this will not execute date": eval 'a="$'"$b"\", well it will if b='";date;:"' – Wildcard Apr 22 '16 at 22:57
  • 1
    @Wildcard Yep, replacing eval by echo: echo 'a="$'"$b"\" gives a="$";date;:"" in which you closed the double quotes, introduced a command and closed the closing double quote. Well done!!. –  Apr 22 '16 at 23:22
  • Note that rm -r / is harmless on most modern OSes (at least Solaris, BSDs and GNUs). Not that I suggest you to check if it is true with yours ;-) – jlliagre Apr 23 '16 at 00:09
  • 1
    @Wildcard Added information about sanitizing variable names. That removes any risk of your example attack. –  Apr 23 '16 at 03:20
0

Eval is an easy solution for bad code. Here is an example that comes to mind:

Say you want to get an array element by reference. You could use eval:

$ nov=(osc pap que)
$ rom=nov
$ eval echo \${$rom[2]}
que

A better way would be:

$ nov=(osc pap que)
$ rom=nov[2]
$ echo ${!rom}
que

A better way still would be:

$ tail -1 <<+
> osc
> pap
> que
> +
que
Zombo
  • 1
  • 5
  • 44
  • 63