Pierre Spring
Pierre Spring

Reputation: 10675

When is eval evil in php?

In all the years I have been developing in PHP, I have always heard that using eval() is evil.

Considering the following code, wouldn't it make sense to use the second (and more elegant) option? If not, why?

// $type is the result of an SQL statement, e.g.
// SHOW COLUMNS FROM a_table LIKE 'a_column';
// hence you can be pretty sure about the consistency
// of your string.

$type = "enum('a','b','c')";

// option one
$type_1 = preg_replace('#^enum\s*\(\s*\'|\'\s*\)\s*$#', '', $type);
$result = preg_split('#\'\s*,\s*\'#', $type_1);

// option two
eval('$result = '.preg_replace('#^enum#','array', $type).';');

Upvotes: 90

Views: 48352

Answers (21)

dkellner
dkellner

Reputation: 9926

EVAL IS FINE,

and it's time to close this debate forever. (I know it's an old topic but yes it's still happening today and it's amazing how people almost religiously deny a helpful thing.)

So, consider this:

  • Ditch include/require?
    When you use any kind of inclusion, it's practically eval(...) with a file. Does that make you stop using them? No, as long as you control what to include. And the list goes on. Just imagine banning copy because "if you copy the wrong thing, you're in trouble"... Really?
    (insert Nicholas Cage meme)

  • How do you not know?
    If you're not sure whether a string can contain malicious code or not, if you can't make sure it doesn't, you're probably not paying enough attention. In this case, sure, don't use eval but be careful with everything else too, including scissors.

  • Parsing for yourself
    There are cases when you want to evaluate something, like a logical or numerical expression, solve all the brackets and precedences, and you could use eval to do your job, like 5*(12/3+7*(45+12)-65*1), this is safe. I can't see how a vicious hacker would breach the Pentagon with nothing but numbers and brackets.

CONCLUSION

  1. As long as you're careful, you can use eval safely.
  2. As long as you're not, it's not safe to take a walk.

Upvotes: -1

Another alternative

reference: ClosureStream

class ClosureStream
{
    const STREAM_PROTO = 'closure';

    protected static $isRegistered = false;

    protected $content;

    protected $length;

    protected $pointer = 0;

    function stream_open($path, $mode, $options, &$opened_path)
    {
        $this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
        $this->length = strlen($this->content);
        return true;
    }

    public function stream_read($count)
    {
        $value = substr($this->content, $this->pointer, $count);
        $this->pointer += $count;
        return $value;
    }

    public function stream_eof()
    {
        return $this->pointer >= $this->length;
    }

    public function stream_set_option($option, $arg1, $arg2)
    {
        return false;
    }

    public function stream_stat()
    {
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    }

    public function url_stat($path, $flags)
    {
        $stat = stat(__FILE__);
        $stat[7] = $stat['size'] = $this->length;
        return $stat;
    }

    public function stream_seek($offset, $whence = SEEK_SET)
    {
        $crt = $this->pointer;

        switch ($whence) {
            case SEEK_SET:
                $this->pointer = $offset;
                break;
            case SEEK_CUR:
                $this->pointer += $offset;
                break;
            case SEEK_END:
                $this->pointer = $this->length + $offset;
                break;
        }

        if ($this->pointer < 0 || $this->pointer >= $this->length) {
            $this->pointer = $crt;
            return false;
        }

        return true;
    }

    public function stream_tell()
    {
        return $this->pointer;
    }

    public static function register()
    {
        if (!static::$isRegistered) {
            static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
        }
    }

}
ClosureStream::register();
// Your code here!
$closure=include('closure://function(){echo "hola mundo";}');
$closure();

Upvotes: 0

thomasrutter
thomasrutter

Reputation: 117343

eval is equally "evil" at all times.

If you see it as evil, then it's equally evil at all times. The reasons many describe it as evil don't go away with context.

Using eval() is generally a bad idea because it decreases readability of code, it impairs the ability for you to predict the code path before runtime (which has possible security implications), and hence affects the ability to analyze and debug code. Using eval() can also prevent the evaluated code and the code surrounding it from being optimised by an opcode cache such as the Zend Opcache integrated into PHP 5.5 and above, or by a JIT compiler such as the one in HHVM.

Furthermore, there is no situation for which it is absolutely necessary to use eval() - PHP is a fully-capable programming language without it. Regardless of what you want to use eval() for, there will be another way of doing it, in PHP.

Whether or not you actually see these as evils or you can personally justify using eval() is up to you. To some, the pitfalls are too great to ever justify it, and to others, eval() is a handy shortcut.

Upvotes: 21

user6355773
user6355773

Reputation:

Most people will point out the fact that it can be dangerous when you are dealing with user input (which is possible to deal with).

For me the worst part is it reduces maintainability of your code:

  • Hard to debug
  • Hard to update
  • Limits usage of tools and helpers (like IDEs)

Upvotes: 0

Jason
Jason

Reputation: 29

Here is a solution to running PHP code pulled from a database without using eval. Allows for all in scope functions and exceptions:

$rowId=1;  //database row id
$code="echo 'hello'; echo '\nThis is a test\n'; echo date(\"Y-m-d\");"; //php code pulled from database

$func="func{$rowId}";

file_put_contents('/tmp/tempFunction.php',"<?php\nfunction $func() {\n global \$rowId;\n$code\n}\n".chr(63).">");

include '/tmp/tempFunction.php';
call_user_func($func);
unlink ('/tmp/tempFunction.php');

Basically it creates a unique function with the included code in a text file, includes the file, calls the function, then removes the file when done with it. I am using this to perform daily database ingestions/syncronizations where every step requires unique code to handle to process. This has solved all the issues I was facing.

Upvotes: 2

Michał Niedźwiedzki
Michał Niedźwiedzki

Reputation: 12939

I would be cautious in calling eval() pure evil. Dynamic evaluation is a powerful tool and can sometimes be a life saver. With eval() one can work around shortcomings of PHP (see below).

The main problems with eval() are:

  • Potential unsafe input. Passing an untrusted parameter is a way to fail. It is often not a trivial task to make sure that a parameter (or part of it) is fully trusted.
  • Trickiness. Using eval() makes code clever, therefore more difficult to follow. To quote Brian Kernighan "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it"

The main problem with actual use of eval() is only one:

  • Inexperienced developers who use it without enough consideration.

As a rule of thumb I tend to follow this:

  1. Sometimes eval() is the only/the right solution.
  2. For most cases one should try something else.
  3. If unsure, goto 2.
  4. Else, be very, very careful.

Upvotes: 136

Francois Bourgeois
Francois Bourgeois

Reputation: 3690

eval() is always evil.

  • for security reasons
  • for performance reasons
  • for readability / reusability reasons
  • for IDE / tool reasons
  • for debugging reasons
  • there is always a better way

Upvotes: 6

Patrick Cornelissen
Patrick Cornelissen

Reputation: 7958

eval is evil when there is only the slightest possibility that userinput is included in the evaluated string. When you do eval without content that came from a user, you should be safe.

Nevertheless you should think at least twice before using eval, it looks deceivingly simple, but with error handling (see VBAssassins comment), debuggability etc. in mind, it is not so simple anymore.

So as a rule of thumb: Forget about it. When eval is the answer you're propably asking the wrong question! ;-)

Upvotes: 43

Xyz
Xyz

Reputation: 6013

Other than security concerns, eval() cannot be compiled, optimized or opcode cached, thus it will allways be slower -- way slower -- than normal php code. It is thus non-performant to use eval, allthough that doesn't make it evil. (goto is evil, eval is only bad practise/smelly code/ugly)

Upvotes: 0

Braincracking
Braincracking

Reputation: 39

It's bad programming that makes eval() evil, not the function. I use it sometimes, as I can not get around it in dynamic programming on multiple sites. I can not have PHP being parsed at one site, as I will not receive the things I want. I would just receive a result! I'm happy a function as eval() exists, as it makes my life much more easy. User-input? Only bad programmers get hooked up by hackers. I don't worry about that.

I predict you will have serious problems soon...

In all honesty, there is absolutely no good use for an exorbitant function such as eval, in an interpreted language such as PHP. I have never seen eval perform program functions which could not have been executed using other, safer ways...

Eval is the root of all evil, I wholeheartedly agree, to all people that think that testing user input will help. Think twice, user input can come in many different forms, and as we speak hackers are exploiting that function you didn't care enough about. In my opinion, just avoid eval altogether.

I have seen crafted examples to abuse the eval function that surpassed my own creativity. From a security stance, avoid at all cost, and I would even go as far as to demand it to be at the very least an option in the PHP configuration, rather than a 'given'.

Upvotes: 2

It's bad programming that makes eval() evil, not the function. I use it sometimes, as I can not get around it in dynamic programming on multiple sites. I can not have PHP being parsed at one site, as I will not receive the things I want. I would just receive a result! I'm happy a function as eval() exists, as it makes my life much more easy. User-input? Only bad programmers get hooked up by hackers. I don't worry about that.

Upvotes: 3

TheHippo
TheHippo

Reputation: 63139

Another reason eval is evil is, that it could not cached by PHP bytecode caches like eAccelertor or ACP.

Upvotes: 2

Alix Axel
Alix Axel

Reputation: 154553

eval() is slow, but I wouldn't call it evil.

It's the bad use we make of it that can lead to code injection and be evil.

A simple example:

$_GET = 'echo 5 + 5 * 2;';
eval($_GET); // 15

A harmlful example:

$_GET = 'system("reboot");';
eval($_GET); // oops

I would advise you to not use eval() but if you do, make sure you validate / whitelist all input.

Upvotes: 14

Harry
Harry

Reputation: 4946

I used to use eval() a lot, but I found most of the cases you don't have to use eval to do tricks. Well, you have call_user_func() and call_user_func_array() in PHP. It's good enough to statically and dynamically call any method.

To perform a static call construct your callback as array('class_name', 'method_name'), or even as simple string like 'class_name::method_name'. To perform a dynamic call use array($object, 'method') style callback.

The only sensible use for eval() is to write a custom compiler. I made one, but eval is still evil, because it's so damn hard to debug. The worst thing is the fatal error in evaled code crashes the code which called it. I used Parsekit PECL extension to check the syntax at least, but still no joy - try to refer to unknown class and whole app crashes.

Upvotes: 1

Nolte
Nolte

Reputation: 1097

PHP advises that you write your code in such a way that it can be executing via call_user_func instead of doing explicit evals.

Upvotes: 3

stefs
stefs

Reputation: 18549

i'll blatantly steal the content here:

  1. Eval by its nature is always going to be a security concern.

  2. Besides security concerns eval also has the problem of being incredibly slow. In my testing on PHP 4.3.10 its 10 times slower then normal code and 28 times slower on PHP 5.1 beta1.

blog.joshuaeichorn.com: using-eval-in-php

Upvotes: 7

Toby
Toby

Reputation: 214

eval evaluates a string as code, the problem with that is if the string is in any way "tainted" it could expose huge security threats. Normally the problem is in a case where user input is evaluated in the string in many cases the user could input code (php or ssi for example) that is then run within eval, it would run with the same permissions as your php script and could be used to gain information/access to your server. It can be quite tricky to make sure user input is properly cleaned out before handing it to eval. There are other problems... some of which are debatable

Upvotes: 3

BlackAura
BlackAura

Reputation: 3208

In this case, eval is probably safe enough, as long as it's never possible for arbitrary columns to be created in a table by a user.

It's not really any more elegant though. This is basically a text parsing problem, and abusing PHP's parser to handle is seems a bit hacky. If you want to abuse language features, why not abuse the JSON parser? At least with the JSON parser, there's no possibility at all of code injection.

$json = str_replace(array(
    'enum', '(', ')', "'"), array)
    '',     '[', ']', "'"), $type);
$result = json_decode($json);

A regular expression is probably the most obvious way. You can use a single regular expression to extract all the values from this string:

$extract_regex = '/
    (?<=,|enum\()   # Match strings that follow either a comma, or the string "enum("...
    \'      # ...then the opening quote mark...
    (.*?)       # ...and capture anything...
    \'      # ...up to the closing quote mark...
    /x';
preg_match_all($extract_regex, $type, $matches);
$result = $matches[1];

Upvotes: 15

Parsingphase
Parsingphase

Reputation: 511

Personally, I think that code's still pretty evil because you're not commenting what it's doing. It's also not testing its inputs for validity, making it very fragile.

I also feel that, since 95% (or more) of uses of eval are actively dangerous, the small potential time saving that it might provide in other cases isn't worth indulging in the bad practice of using it. Plus, you'll later have to explain to your minions why your use of eval is good, and theirs bad.

And, of course, your PHP ends up looking like Perl ;)

There are two key problems with eval(), (as an "injection attack" scenario):

1) It may cause harm 2) It may simply crash

and one that's more-social-than-technical:

3) It'll tempt people to use it inappropriately as a shortcut elsewhere

In the first case, you run the risk (obviously, not when you're eval'ing a known string) of arbitrary code execution. Your inputs may not be as known or as fixed as you think, though.

More likely (in this case) you'll just crash, and your string will terminate with a gratuitously obscure error message. IMHO, all code should fail as neatly as possible, failing which it should throw an exception (as the most handleable form of error).

I'd suggest that, in this example, you're coding by coincidence rather than coding to behaviour. Yes, the SQL enum statement (and are you sure that field's enum? - did you call the right field of the right table of the right version of the database? Did it actually answer?) happens to look like array declaration syntax in PHP, but I'd suggest what you really want to do is not find the shortest path from input to output, but rather tackle the specified task:

  • Identify that you have an enum
  • Extract the inner list
  • Unpack the list values

Which is roughly what your option one does, but I'd wrap some if's and comments around it for clarity and safety (eg, if the first match doesn't match, throw exception or set null result).

There are still some possible issues with escaped commas or quotes, and you should probably unpack the data then de-quote it, but it does at least treat data as data, rather than as code.

With the preg_version your worst outcome is likely to be $result=null, with the eval version the worst is unknown, but at least a crash.

Upvotes: 4

user83632
user83632

Reputation:

I'd also pay some consideration to people maintaining your code.

eval() isn't the easiet to just look at and know what is supposed to happen, your example isn't so bad, but in other places it can be a right nightmare.

Upvotes: 4

GreenieMeanie
GreenieMeanie

Reputation: 3610

When you are using foreign data (such as user input) inside the eval.

In your example above, this isn't an issue.

Upvotes: 12

Related Questions