php 访问php_PHP的美味邪恶

php 访问php

This article was peer reviewed by Wern Ancheta and Deji Akala. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

本文由Wern AnchetaDeji Akala进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!



I want to look at two PHP functions: eval and exec. They’re so often thrown under the sensible-developers-never-use-these bus that I sometimes wonder how many awesome applications we miss out on.

我想看看两个PHP函数: evalexec 。 它们经常被扔在明智的开发人员从未使用过的总线之下,我有时想知道我们错过了多少出色的应用程序。

Like every other function in the standard library, these have their uses. They can be abused. Their danger lies in the amount of flexibility and power they offer even the most novice of developers.

像标准库中的所有其他函数一样,它们都有其用途。 他们可能被滥用。 他们的危险在于他们甚至为开发人员的新手提供的灵活性和功能。

Let me show you some of the ways I’ve seen these used, and then we can talk about safety precautions and moderation.

让我向您展示一些我所看到的使用方式,然后我们可以讨论安全预防措施和节制措施。

Evil elephpant

动态类创建 (Dynamic Class Creation)

The first time I ever saw dynamic class creation was in the bowels of CodeIgniter. At the time, CodeIgniter was using it to create ORM classes. eval is still used to rewrite short open tags for systems that don’t have the feature enabled…

我第一次看到动态类的创建是在CodeIgniter的肠道中。 当时,CodeIgniter使用它来创建ORM类。 eval仍用于为未启用该功能的系统重写短时打开的标签

More recently, though, my friend Adam Wathan tweeted about using it to dynamically create Laravel facades. Take a look at what the classes usually look like:

不过,最近,我的朋友Adam Wathan在推特上发布了有关使用它动态创建Laravel外墙的推 。 看一看这些类通常是什么样的:

namespace Illuminate\Support\Facades;

class Artisan extends Facade
{
    protected static function getFacadeAccessor()
    {
        return "Illuminate\Contracts\Console\Kernel";
    }
}

This is from github.com/laravel/framework/blob/5.3/src/Illuminate/Support/Facades/Artisan.php

这是来自github.com/laravel/framework/blob/5.3/src/Illuminate/Support/Facades/Artisan.php

These facade classes aren’t facades in the traditional sense, but they do act as static references to objects stored in Laravel’s service locator class. They project an easy way to refer to objects defined and configured elsewhere, and have benefits over traditional static (or singleton) classes. One of these benefits is in testing:

这些外观类不是传统意义上的外观,但是它们确实充当对Laravel 服务定位器类中存储的对象的静态引用。 它们提供了一种简单的方法来引用在其他地方定义和配置的对象,并且比传统的静态(或单例)类更具优势。 这些好处之一是在测试中:

public function testNotificationWasQueued()
{
    Artisan::shouldReceive("queue")
        ->once()
        ->with(
            "user:notify",
            Mockery::subset(["user" => 1])
        );

    $service = new App\Service\UserService();
    $service->notifyUser(1);
}

…and though these facades are simple to create, there are a lot of them. That’s not the kind of code I find interesting to write. It seems Adam felt the same what when we wrote the tweet.

…尽管这些立面很容易创建,但是有很多。 我觉得这不是那种有趣的代码。 当我们写推文时,亚当似乎也有同感。

So, how could we create these facade classes dynamically? I haven’t seen Adam’s implementation code, but I’m guessing it looks something like:

那么,我们如何动态创建这些外观类呢? 我还没有看到Adam的实现代码,但是我猜它看起来像这样:

function facade($name, $className) {
    if (class_exists($name)) {
        return;
    }

    eval("
        class $name extends Facade
        {
            protected static function getFacadeAccessor()
            {
                return $className::class;
            }
        }
    ");
}

That’s a neat trick. Whether of not you use (or even like) Laravel facades, I’m guessing you can see the benefits of writing less code. Sure, this probably adds to the execution time of each request, but we’d have to profile performance to decide if it even matters much.

真是个绝招。 无论您是否使用(甚至不喜欢)Laravel外观,我都在猜测您会看到编写更少代码的好处。 当然,这可能会增加每个请求的执行时间,但是我们必须对性能进行分析,以决定它是否更重要。

I’ve used a similar trick before, when working on a functional programming library. I wanted to create the supporting code to enable me to write applications using fewer (if any) classes. This is what I used eval to create:

在使用函数式编程库时,我曾经使用过类似的技巧。 我想创建支持代码,使我能够使用更少(如果有)的类来编写应用程序。 这是我使用eval创建的内容:

functional\struct\create("person", [
    "first_name" => "string",
    "last_name" => "string",
]);

$me = person([
    "first_name" => "christopher",
    "last_name" => "pitt",
]);

I wanted these structures to be accessible from anywhere, and self-validating. I wanted the freedom to be able to pass in an array of properties, and the control to be able to reject invalid types.

我希望可以从任何地方访问这些结构并进行自我验证。 我希望自由能够传递属性数组,并希望控件能够拒绝无效类型。

The following code uses many instances of ƒ. It’s a unicode character I’ve used as a sort of pseudo-namespace for properties and methods I can’t make private, yet don’t want others to access directly. It’s interesting to know that most unicode characters are valid characters for method and property names.

以下代码使用ƒ许多实例。 这是一个Unicode字符,我已经将其用作无法命名为私有属性,但又不希望其他人直接访问的属性和方法的伪命名空间。 有趣的是,大多数Unicode字符对于方法和属性名称都是有效的字符。

To allow this, I created the following code:

为此,我创建了以下代码:

abstract class ƒstruct
{
    /**
     * @var array
     */
    protected $ƒdef = [];

    /**
     * @var array
     */
    protected $ƒdata = [];

    /**
     * @var array
     */
    protected $ƒname = "structure";

    public function __construct(array $data)
    {
        foreach ($data as $prop => $val) {
            $this->$prop = $val;
        }

        assert($this->ƒthrow_not_all_set());
    }

    private function ƒthrow_not_all_set()
    {
        foreach ($this->ƒdef as $prop => $type) {
            $typeIsNotMixed = $type !== "mixed";
            $propIsNotSet = !isset($this->ƒdata[$prop]);

            if ($typeIsNotMixed and $propIsNotSet) {
                // throw exception
            }
        }

        return true;
    }

    public function __set($prop, $value)
    {
        assert($this->ƒthrow_not_defined($prop, $value));
        assert($this->ƒthrow_wrong_type($prop, $value));

        $this->ƒdata[$prop] = $value;
    }

    private function ƒthrow_not_defined(string $prop)
    {
        if (!isset($this->ƒdef[$prop])) {
            // throw exception
        }

        return true;
    }

    private function ƒthrow_wrong_type(string $prop, $val)
    {
        $type = $this->ƒdef[$prop];

        $typeIsNotMixed = $type !== "mixed";
        $typeIsNotSame = $type !== type($val);

        if ($typeIsNotMixed and $typeIsNotSame) {
            // throw exception
        }

        return true;
    }

    public function __get($prop)
    {
        if ($property === "class") {
            return $this->ƒname;
        }

        assert($this->ƒthrow_not_defined($prop));

        if (isset($this->ƒdata[$prop])) {
            return $this->ƒdata[$prop];
        }

        return null;
    }
}

function type($var) {
    $checks = [
        "is_callable" => "callable",
        "is_string" => "string",
        "is_integer" => "int",
        "is_float" => "float",
        "is_null" => "null",
        "is_bool" => "bool",
        "is_array" => "array",
    ];

    foreach ($checks as $func => $val) {
        if ($func($var)) {
            return $val;
        }
    }

    if ($var instanceof ƒstruct) {
        return $var->class;
    }

    return "unknown";
}

function create(string $name, array $definition) {
    if (class_exists("\\ƒ" . $name)) {
        // throw exception
    }

    $def = var_export($definition, true);

    $code = "
        final class ƒ$name extends ƒstruct {
            protected \$ƒdef = $def;
            protected \$ƒname = '$name';
        }

        function $name(array \$data = []) {
            return new ƒ$name(\$data);
        }
    ";

    eval($code);
}

This is similar to code found at github.com/assertchris/functional-core

这类似于在github.com/assertchris/functional-core上找到的代码

There’s a lot going on here, so let’s break it down:

这里有很多事情,所以让我们分解一下:

  1. The ƒstruct class is the abstract basis for these self-validating structures. It defines __get and __set behavior that includes checks for presence and validity of the data used to initialize each struct.

    ƒstruct类是这些自验证结构的抽象基础。 它定义了__get__set行为,包括检查用于初始化每个结构的数据的存在性和有效性。

  2. When a struct is created, ƒstruct checks if all required properties have been provided. That is, unless any of the properties are mixed they must be defined.

    当创建一个结构, ƒstruct如果所有要求的特性提供了检查。 也就是说,除非将任何属性混合在一起,否则必须对其进行定义。

  3. As each property is set, the value provided is checked against the expected type for that property.

    设置每个属性后,将根据该属性的预期类型检查提供的值。
  4. All of these checks are designed to work with (and wrapped in) calls to assert. This means the checks are only performed in development environments.

    所有这些检查都旨在与assert一起使用(并包装在其中)。 这意味着仅在开发环境中执行检查。

  5. The type function is used to return predictable type strings for the most common types of variables. In addition, if the variable is a subclass of ƒstruct, the ƒname property value is returned as the type string. This means we can define nested structures as easily as: create("account", ["holder" => "person"]). A caveat is that the pre-defined types (like "int" and "string") will always be resolved before structures of the same name.

    type函数用于为最常见的变量类型返回可预测的类型字符串。 另外,如果变量是ƒstruct的子类,则ƒname属性值将作为类型字符串返回。 这意味着我们可以轻松定义嵌套结构: create("account", ["holder" => "person"]) 。 需要注意的是,预定义类型(例如"int""string" )将始终在同名结构之前进行解析。

  6. The create function uses eval to create new subclasses of ƒstruct, containing the appropriate class name, ƒname, and ƒdef. var_export takes the value of a variable and returns the syntax string form of it.

    create函数使用eval创建ƒstruct新子类,其中包含适当的类名, ƒnameƒdefvar_export获取变量的值并返回其语法字符串形式。

The assert function is usually disabled in production environments by having zend.assertions at 0 in php.ini. If you’re not seeing assertion errors where you expect them, check what this setting is set to.

通常在生产环境中通过在php.ini zend.assertions为0来禁用assert函数。 如果未在期望的位置看到断言错误,请检查此设置设置为什么。

领域特定语言 (Domain Specific Languages)

Domain Specific Languages (or DSLs as they’re usually referred to) are alternative programming languages that express an idea or problem domain well. Markdown is an excellent example of this.

领域特定语言(或通常称为DSL的DSL)是可以很好地表达思想或问题领域的替代编程语言。 Markdown是一个很好的例子。

I’m writing this post in Markdown, because it allows me to define the meaning and importance of each bit of text, without getting bogged down in the visual appearance of the post.

我在Markdown中写这篇文章,因为它使我能够定义每一段文字的含义和重要性,而不会陷入帖子的视觉外观中。

CSS is another excellent DSL. It provides many and varied means of addressing one or more HTML elements (by a selector), so that visual styles can be applied to them.

CSS是另一个出色的DSL。 它提供了多种方法(通过选择器)来处理一个或多个HTML元素,因此可以将视觉样式应用于它们。

DSLs can be internal or external. Internal DSLs use an existing programming language as their syntax, but they are uniquely structured within that syntax. Fluent interfaces are a good example of this:

DSL可以是内部或外部的。 内部DSL使用现有的编程语言作为其语法,但是它们在该语法中具有唯一的结构。 流利的接口就是一个很好的例子:

Post::where("is_published", true)
    ->orderBy("published_at", "desc")
    ->take(6)
    ->skip(12)
    ->get();

This is an example of some code you might see in a Laravel application. It’s using an ORM called Eloquent, to build a query for a SQL database.

这是您可能在Laravel应用程序中看到的一些代码示例。 它使用称为EloquentORM来构建SQL数据库查询。

External DSLs use their own syntax, and need some kind of parser or compiler to transform this syntax into machine code. SQL syntax is a good example of this:

外部DSL使用它们自己的语法,并且需要某种解析器或编译器才能将此语法转换为机器代码。 SQL语法就是一个很好的例子:

SELECT * FROM posts
    WHERE is_published = 1
    ORDER BY published_at DESC
    LIMIT 12, 6;

The above PHP code should approximately render to this SQL code. It’s sent over the wire to a MySQL server, which transforms it into code servers can understand.

上面PHP代码应大致呈现为该SQL代码。 它通过电线发送到MySQL服务器,然后将其转换为代码服务器可以理解的代码。

If we wanted to make our own external DSL, we would need to transform custom syntax into code a machine can understand. Short of learning how assembler works, we could translate custom syntax into a lower-level language. Like PHP.

如果要创建自己的外部DSL,则需要将自定义语法转换为机器可以理解的代码。 在不了解汇编程序如何工作的情况下,我们可以将自定义语法转换为较低级的语言。 像PHP。

Imagine we wanted to make a language that was a super-set language. That means the language would support everything PHP does, but also a few extra bits of syntax. A small example could be:

想象一下,我们想制作一种超级语言。 这意味着该语言将支持PHP所做的一切,而且还支持一些语法。 一个小例子可能是:

$numbers = [1, 2, 3, 4, 5];
print_r($numbers[2..4]);

How could we convert this into valid PHP code? I answered this exact question in a previous post, but the gist of it is by using code similar to:

我们如何将其转换为有效PHP代码? 我在上一篇文章中回答了这个确切的问题,但是要点是使用类似于以下内容的代码:

function replace($matches) {
    return '
        call_user_func(function($list) {
            $lower = '.explode('..', $matches[2])[0].';
            $upper = '.explode('..', $matches[2])[1].';
            return array_slice(
                $list, $lower, $upper - $lower
            );
        }, $'.$matches[1].')
    ';
}

function parse($code) {
    $replaced = preg_replace_callback(
        '/\$(\S+)\[(\S+)\]/', 'replace', $code
    );

    eval($replaced);
}

parse('
    $numbers = [1, 2, 3, 4, 5];
    print_r($numbers[2..4]);
');

This code takes a string of PHP-like syntax and parses it by replacing new syntax with standard PHP syntax. Once the syntax is standard PHP, the code can be evaluated. It essentially does an inline code replacement, which is only possible when code can be executed dynamically.

此代码采用类似于PHP的语法字符串,并通过用标准PHP语法替换新语法来对其进行解析。 一旦语法是标准PHP,就可以评估代码。 它本质上执行内联代码替换,只有当代码可以动态执行时才有可能。

To do this, without the eval function, we’d need to build a compiler. Something that takes high-level code and gives back low-level code. In this case, it would need to take our PHP super-set language code, and give back valid PHP code.

为此,如果没有eval函数,我们需要构建一个编译器。 需要高级代码并返回低级代码的东西。 在这种情况下,需要使用我们PHP超集语言代码,并退还有效PHP代码。

平行性 (Parallelism)

Let’s take a look at another jaded core function: exec. Perhaps more decried than even eval, exec is universally denounced by all but the more adventurous developers. And I have to wonder why.

让我们看一下另一个烦人的核心功能: exec 。 也许比eval更受谴责,除了冒险精神更强的开发人员以外,所有人都普遍谴责exec 。 我想知道为什么。

In case you’re unfamiliar, exec works like this:

如果您不熟悉, exec工作方式如下:

exec("ls -la | wc -l", $output);
print $output[0]; // number of files in the current dir

exec is a way for PHP developers to run an operating system command, in a new sub-process of the current script. With a little bit of prodding, we can actually make this sub-process run completely in the background:

exec是PHP开发人员在当前脚本的新子过程中运行操作系统命令的一种方式。 有了一点点建议,我们实际上可以使此子流程完全在后台运行:

exec("sleep 30 > /dev/null 2> /dev/null &");

To do this: we redirect stdout and stderr to /dev/null and add an & to the end of the command we want to run in the background. There are many reasons you’d want to do something like this, but my favorite is to be able to perform slow and/or blocking tasks away from the main PHP process.

为此:我们将stdoutstderr重定向到/dev/null并在要在后台运行的命令的末尾添加& 。 有许多原因想要执行这样的操作,但是我最喜欢的是能够执行缓慢的和/或阻止主要PHP进程执行的任务。

Image you had a script like this:

图像中您具有如下脚本:

foreach ($images as $image) {
    $source = imagecreatefromjpeg($image["src_path"]);

    $icon = imagecreatetruecolor(64, 64);

    imagecopyresampled(
        $source, $icon, 0, 0, 0, 0,
        64, 64, $image["width"], $image["height"]
    );

    imagejpeg($icon, $image["ico_path"]);

    imagedestroy($icon);
    imagedestroy($source);
}

This is fine, for a few images. But imagine hundreds of images, or dozens of requests per second. Traffic like that could easily affect server performance. In cases like these, we can isolate slow code and run it in parallel (or even remotely) to user-facing code.

对于一些图像,这很好。 但是,想象一下数百个图像或每秒数十个请求。 这样的流量很容易影响服务器性能。 在这种情况下,我们可以隔离慢代码并与面向用户的代码并行(甚至远程)运行。

Here’s how we could run the slow code:

这是我们运行慢速代码的方法:

exec("php slow.php > /dev/null 2> /dev/null &");

We could even take it a step further by generating a dynamic script for the PHP command-line interface to run. To begin with, we can install SuperClosure :

我们甚至可以通过为PHP命令行界面生成动态脚本来运行来使它更进一步。 首先,我们可以安装SuperClosure

require __DIR__ . '/vendor/autoload.php';

use SuperClosure\Serializer;

function defer(Closure $closure) {
    $serializer = new Serializer();
    $serialized = $serializer->serialize($closure);

    $autoload = __DIR__ . '/vendor/autoload.php';

    $raw = '
        require \'' . $autoload . '\';

        use SuperClosure\Serializer;

        $serializer = new Serializer();
        $serialized = \'' . $serialized . '\';

        call_user_func(
            $serializer->unserialize($serialized)
        );
    ';

    $encoded = base64_encode($raw);

    $script = 'eval(base64_decode(\'' . $encoded . '\'));';

    exec('php -r "' . $script . '"', $output);

    return $output;
}

$output = defer(function() {
    print "hi";
});

Why do we need to hard-code a script (to run in parallel) when we could just dynamically generate the code we want to run, and pipe it directly into the PHP binary?

当我们可以动态生成要运行的代码并将其直接通过管道传递到PHP二进制文件时,为什么需要对脚本进行硬编码(并行运行)?

We can even combine this exec trick with eval, by encoding the source code we want to run, and decoding it upon execution. This makes the command to start the sub-process much neater overall.

我们甚至可以将此exec技巧与eval结合使用,方法是对要运行的源代码进行编码,并在执行时对其进行解码。 这使启动子过程的命令总体上变得更加整洁。

We can even add a unique identifier, so that the sub-process is easier to track and kill:

我们甚至可以添加一个唯一的标识符,以便子流程更易于跟踪和终止:

function defer(Closure $closure, $id = null) {
    // create $script

    if (is_string($id)) {
        $script = '/* id:' . $id . ' */' . $script;
    }

    $shh = '> /dev/null 2> /dev/null &';

    exec(
        'php -r "' . $script . '" ' . $shh,
        $output
    );

    return $output;
}

保持安全 (Staying Safe)

The main reason so many developers dislike and/or advise against eval and exec is because their misuse leads to far more disastrous outcomes than, say, count.

如此多的开发人员不喜欢和/或建议evalexec的主要原因,是因为他们的滥用导致的灾难性后果远远超过count

I’d suggest, instead of listening to these folks and immediately dismissing eval and exec, you learn how to use them securely. The main thing you want to avoid is using them with unfiltered user-supplied input.

我建议不要学习这些人并立即解雇evalexec ,而是学习如何安全地使用它们。 您要避免的主要事情是将它们与未经过滤的用户提供的输入一起使用。

Avoid at all costs:

不惜一切代价避免:

exec($_GET["op"] . " " . $_GET["path"]);

Try instead:

请尝试:

$op = $_GET["op"];
$path = $_GET["path"];

if (allowed_op($op) and allowed_path($path)) {
    $clean = escapeshellarg($path);

    if ($op === "touch") {
        exec("touch {$clean}");
    }

    if ($op === "remove") {
        exec("rm {$clean}");
    }
}

…or better yet: avoid putting any user-supplied data directly into an exec command! You can also try other escaping functions, like escapeshellcmd. Remember that this is a gateway into your system. Anything the user running the PHP process is allowed to do, exec is allowed to do. That’s why it’s intentionally disabled on shared hosting.

…或更妙的是:避免将任何用户提供的数据直接放入exec命令! 您还可以尝试其他转义功能,例如escapeshellcmd 。 请记住,这是通向您系统的网关。 允许运行PHP进程的用户执行任何操作,允许执行exec 。 这就是为什么在共享主机上有意禁用它的原因。

As with all PHP core functions, use these in moderation. Pay special attention to the data you allow in, and avoid unfiltered user-supplied data in exec. But, don’t avoid the functions without understanding why you’re avoiding them. That’s not helpful for you or the people who will learn from you.

与所有PHP核心功能一样,适度使用这些功能。 要特别注意允许的数据,并避免在exec未经过滤的用户提供的数据。 但是,在不理解为什么要避免使用它们的情况下,请不要回避这些功能。 这对您或将向您学习的人没有帮助。

翻译自: https://www.sitepoint.com/the-delicious-evils-of-php/

php 访问php

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值