Php 中的 eval evil 是什么时候?

这些年我一直在 PHP 开发,我总是听说使用 eval()是邪恶的。

考虑到下面的代码,使用第二个(更优雅的)选项是否有意义? 如果没有,为什么?

// $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).';');
46638 次浏览

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

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

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! ;-)

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.

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.

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];

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

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

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.

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.

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.

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.

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.

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

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.

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'.

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)

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

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.

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)

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();