![](/confluence/download/attachments/88042725/wiki_logo.gif)
Perl's eval
built-in form provides programs with access to Perl's internal parser and evaluator. It may be called with a scalar argument (that is, a string) or with an expression that evaluates to a scalar argument, or it may be called with a block.
The eval
built-in has one important role. It traps any errors that would otherwise be fatal to the program and stores them in the $@
package variable. This role means that eval
is critical to proper exception handling. If the code being evaluated is itself invalid (perhaps because it contains a syntax error), again, the error does not cause program termination, but is instead trapped by eval
and saved in the $@
variable.
When invoked with a block, the Perl parser compiles the block argument at the same time that it compiles the rest of the code, before it begins execution. Consequently, syntax errors or other compile-time errors are reported during compilation, before the program has begun executing.
However, when eval
invoked with a string argument, the argument is parsed and compiled only when the eval
form actually executes, at run-time. Consequently, a syntax error is reported only when the eval
form is actually executed. Furthermore, the argument gets compiled every time that eval
is executed. Consequently, the block form of eval
has better performance and reliability than the string form of eval
.
But, these issues aside, the string form of eval
also allows it to execute any code. If an attacker can control the value of a scalar argument to eval
, the attacker can cause any arbitrary code to be executed with the privileges of the running program. Therefore, the string form of eval
must not be used.
Noncompliant Code Example
Perl normally signals a fatal error if division by zero occurs. The eval
built-in can be used to prevent such an error from terminating the program. This noncompliant code example uses the string-based eval
to reduce the severity of a division-by-zero error to a mere warning.
my $a = $ARGV[0]; my $b = $ARGV[1]; my $answer = 0; eval qq{ \$answer = $a / $b }; carp $@ if $@; print "The quotient is $answer\n";
As shown below, when given normal input, this program behaves as expected:
% ./divide.pl 18 3 The quotient is 6 %
It also gracefully handles division by zero:
% ./divide.pl 18 0 Illegal division by zero at (eval 1) line 1. The quotient is 0 %
But it also allows the caller to invoke arbitrary Perl code:
% ./divide.pl 18 '6 ; print "Surprise!\n"' Surprise! The quotient is 3 %
Compliant Solution
This compliant solution uses the block-based form of eval
. In addition to foiling any attempts to evaluate untrusted code, this form of eval
parses its argument at compile time rather than run-time. Performance is improved, and any syntax errors with the code are still caught and reported.
my $a = $ARGV[0]; my $b = $ARGV[1]; my $answer = 0; eval { $answer = $a / $b; }; carp $@ if $@; print "The quotient is $answer\n";
As shown below, this code behaves as in the previous example, but the division operation causes a warning when given non-numeric (malicious) input and ignores the malicious code.
% ./divide.pl 18 3 The quotient is 6 % ./divide.pl 18 0 Illegal division by zero at ./divide.pl line 12. The quotient is 0 % ./divide.pl 18 '6 ; print "Surprise!\n"' Argument "6 ; print "Surprise!\\n"" isn't numeric in division (/) at ./divide.pl line 12. The quotient is 3 %
Noncompliant Code Example
This noncompliant code example attempts to load a module that is specified by a variable.
my $module = "Foo::Bar"; # ... require $module;
This code does not behave properly, as require
does no pathname interpolation when loading a module specified by a variable, and so it attempts to search the filesystem for a file named Foo::Bar
.
Noncompliant Code Example
The perlfunc manpage recommends using the string-based form of eval
when importing a module with require
or use
, where the name of the module may be stored in a variable. To wit:
If EXPR is a bareword, the require assumes a ".pm" extension and replaces "::" with "/" in the filename for you, to make it easy to load standard modules. ... In other words, if you try this:
require Foo::Bar; # a splendid bareword
the require function will actually look for the "Foo/Bar.pm" file in the directories specified in the
@INC
array.But if you try this:
$class = 'Foo::Bar';
require $class; # $class is not a bareword
# or
require "Foo::Bar"; # not a bareword because of the ""
the require function will look for the "Foo::Bar" file in the
@INC
array and will complain about not finding "Foo::Bar" there. In this case you can do:
eval "require $class";
This noncompliant code example uses eval
to load a module specified by a variable.
my $module = "Foo::Bar"; # ... eval "require $module";
While this code properly searches the include paths for the file Foo/Bar.pm
, it also suffers from command injection, as shown in the following transcript:
% perl -e 'eval "use $ARGV[0]";' 'Foo::Bar' package Foo::Bar loaded % perl -e 'eval "use $ARGV[0]";' 'Foo::Bar ; print "Surprise!\n"' package Foo::Bar loaded Surprise! %
Compliant Solution
This compliant solution uses the built-in Module::Load
package which provides the ability to import modules specified by a variable. The load
function prevents command injection.
use Module::Load; my $module = "Foo::Bar"; # ... load $module;
Exceptions
IDS35-PL-EX0: This rule specifically forbids passing a scalar string or expression to eval
. It does not forbid passing a block to eval
.
eval $x ; # string-based, noncompliant, evaluates value of $x eval "$x"; # string-based, noncompliant, evaluates value of $x eval '$x'; # string-based, noncompliant, returns value of $x (unevaluated) eval {$x}; # block-based, compliant, returns value of $x (unevaluated)
Risk Assessment
Using string-based eval
can lead to arbitrary code execution.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
IDS35-PL | high | likely | medium | P18 | L1 |
Automated Detection
Tool | Diagnostic |
---|---|
Perl::Critic | BuiltinFunctions::ProhibitStringyEval |
Taint mode | Insecure dependency in eval |
Bibliography
[Conway 2005] | "String Evaluations," p. 161 |
[VU#671444] | Input validation error in quikstore.cgi allows attackers to execute commands |
[Wall 2011] | perlfunc |
4 Comments
Jeffrey Thalhammer
Under IDS35:EX0, it says this code is evaluated as block and therefore acceptable:
eval
'$x'
;
That is not true. It's a string. The string just isn't being interpolated. But that doesn't mean it can't be abuse
d.
David Svoboda
I fixed this exception.
eval '$x'
is indeed a string, not a block. I don't think it can be used by an attacker to run arbitrary code. On the other paw, it is a useless feature, as it returns the value$x
unevaluated. So it should still be noncompliant, as only the block form ofeval
should be used.Flavio Poletti
However, it appears to be the only way to load modules that are not specified by barewords
There are modules to provide this kind of functionality out of the box (e.g. Module::Load, Class::Load and probably many others that I don't know about) or it is possible to use Module::Runtime (via module_notional_filename) to transform the module/class name to a string that is OK with require.
David Svoboda
Thanks for the info. I've replaced that exception with code samples showing the bad way to load files, with a good way that uses Module::Load.