Swoole Compiler加密Drupal产生的一些问题

栏目: PHP · 发布时间: 4年前

内容简介:上个星期碰到个客户使用注:

前言

上个星期碰到个客户使用 Swoole Compiler 加密 Drupal 导致 Drupal 项目无法运行的问题,逐步排查后总结问题是 Drupal 中有部分代码直接通过 file_get_contents 获取 PHP 源码导致的,因为项目代码是加密过后的,所以直接获取 PHP 源码解析是获取不到想要的内容的。

注:

Drupal 是使用 PHP 语言编写的开源内容管理框架( CMF ),它由内容管理系统( CMS )和 PHP 开发框架( Framework )共同构成。

加密后的影响 Drupal 运行的主要代码

  • 代码路径: drupal/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php:126

    //代码内容
    protected function parse()
    {
        if ($this->parsed || !$fileName = $this->finder->findFile($this->className)) {
            return;
        }
        $this->parsed = true;
        $contents = file_get_contents($fileName);
        if ($this->classAnnotationOptimize) {
            if (preg_match("/\A.*^\s*((abstract|final)\s+)?class\s+{$this->shortClassName}\s+/sm", $contents, $matches)) {
                $contents = $matches[0];
            }
        }
        $tokenParser = new TokenParser($contents);
        ......
    }

    其中部分代码如上,通过 class 名获取文件路径,然后通过 file_get_contents 获取 PHP 文件的内容,其中 TokenParser 类中构造函数如下

    public function __construct($contents)
    {
         $this->tokens = token_get_all($contents);
         token_get_all("<?php\n/**\n *\n */");
         $this->numTokens = count($this->tokens);
    }

    传入获取到的源码通过 token_get_all 进行解析,然后后续分析代码获取 PHP 文件的类、属性、方法的注释 ,父类的命名空间 和 class 名 ,本类的 use 信息等,因为文件已经加密,所以 file_get_contents 获取到的内容是加密后的内容, token_get_all 就解析不到正确的信息,从而导致程序无法运行。

  • 解决方案:

    本次使用的 2.1.1 版本的加密器,通过 Swoole Compiler 加密器加密的代码,在配置文件中 save_doc 配置选项必须设置为 1 ,如果设置为 0 则不会保存注释,并且在 2.1.3 版本 swoole_loader.so 扩展中新增加的函数 naloinwenraswwww 也无法获取到类中use的相关信息,具体函数使用在后面会详细说明。

    1    $ref = new \ReflectionClass($this->className);
     2
     3    $parent_ref = $ref->getParentClass();
     4
     5    ......
     6
     7    if (is_file($fileName)) {
     8        $php_file_info = unserialize(naloinwenraswwww(realpath($fileName)));
     9        foreach ($php_file_info as $key => $info) {
     10           if ($key == 'swoole_namespaces' || $key == 'swoole_class_name') {
     11               continue;
     12           }
     13           $this->useStatements[$key] = $info;
     14       }
     15   }
     16
     17   $this->parentClassName = $parent_ref->getName();
     18
     19   if (strpos($this->parentClassName, '\\')!==0) {
     20       $this->parentClassName = '\\'.$this->parentClassName;
     21   }
     22
     23   $static_properties = [];
     24
     25   $properties = $ref->getProperties();
     26
     27   $parent_properties = $this->createNewArrKey($parent_ref->getProperties());
     28
     29   ......
     30
     31   $static_methods = [];
     32
     33   $methods = $ref->getMethods();
     34
     35   ......

    1.第1行通过类名来获取反射类 ReflectionClass 类的对象。

    2.因为此反射类包含了所有父类中的属性和方法,但源码中只要获取本类中的属性和方法,所以还要获取父类的反射类然后通过对比来剔除父类中的属性和方法,第3行使用 ReflectionClass 类提供的 getParentClass 方法获取父类的反射类,此方法返回父类的 ReflectionClass 对象。

    3.第25行通过 ReflectionClass 类提供的 getProperties 方法分别获取本类和父类中的属性,然后进行对比剔除父类的属性,保留本类的属性,此方法返回的是一个 ReflectionProperty 类对象。

    4.通过 ReflectionProperty 类提供的 getDocComment 方法就可以拿到属性的注释。

    5.同上第33行通过 ReflectionClass 类提供的 getMethods 方法可以拿到本类和父类中的方法,然后进行对比剔除父类的方法,保留本类的方法,此方法返回的是一个 ReflectionMethod 类对象。

    6.通过 ReflectionMethod 对象提供的 getDocComment 方法就可以拿到方法的注释。

    7.通过第17行 ReflectionClass 提供的 getName 方法可以拿到类名。

    ps:因为反射无法获取 use 类的信息,所以在 2.1.3 版本中的 swoole_loader.so 扩展中添加函数 naloinwenraswwww ,此函数传入一个 PHP 文件的绝对路径,返回传入文件的相关信息的序列化数组,反序列化后数组如下

    [
        "swoole_namespaces" => "Drupal\Core\Datetime\Element",
        "swoole_class_name" => "Drupal\Core\Datetime\Element\DateElementBase",
        "nestedarray" => "Drupal\Component\Utility\NestedArray",
        "drupaldatetime" => "Drupal\Core\Datetime\DrupalDateTime",
        "formelement"=> "Drupal\Core\Render\Element\FormElement"
    ]

    其中 swoole_namespaces 为文件的命名空间, swoole_class_name 为文件的命名空间加类名,其他为 use 信息,键为 use 类的类名小写字母,如存在别名则为别名的小写字母,值为 use 类的命名空间加类名,通过该函数和反射函数可以兼容 StaticReflectionParser 中加密后出现的无法获取正确信息的问题

在加密后的未影响 Drupal 运行的潜在问题:

drupal/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php:39
drupal/vendor/symfony/class-loader/ClassMapGenerator.php:91
drupal/vendor/symfony/routing/Loader/AnnotationFileLoader.php:90

Drupal 中引入了 Symfony 框架,此框架中部分代码也是通过 file_get_contentstoken_get_all 来获取 PHP 文件的类名,但目前未对 Druapl 运行产生影响,可能并未用到其中方法

  • 解决方案:

    StaticReflectionParser 类的解决方案一样通过 2.1.3 版本中的 swoole_loader.so 扩展中添加函数 naloinwenraswwww 来获取加密后文件的命名空间和类名

尚未有更好方案的问题:

  • 代码路径: drupal/core/includes/install.inc:220

    function drupal_rewrite_settings($settings = [], $settings_file = NULL)
    {
        if (!isset($settings_file)) {
            $settings_file = \Drupal::service('site.path') . '/settings.php';
        }
        // Build list of setting names and insert the values into the global namespace.
        $variable_names = [];
        $settings_settings = [];
        foreach ($settings as $setting => $data) {
            if ($setting != 'settings') {
                _drupal_rewrite_settings_global($GLOBALS[$setting], $data);
            } else {
                _drupal_rewrite_settings_global($settings_settings, $data);
            }
            $variable_names['$' . $setting] = $setting;
        }
        $contents = file_get_contents($settings_file);
        if ($contents !== FALSE) {
            // Initialize the contents for the settings.php file if it is empty.
            if (trim($contents) === '') {
                $contents = "<?php\n";
            }
            // Step through each token in settings.php and replace any variables that
            // are in the passed-in array.
            $buffer = '';
            $state = 'default';
            foreach (token_get_all($contents) as $token) {
                if (is_array($token)) {
                    list($type, $value) = $token;
                } else {
                    $type = -1;
                    $value = $token;
                }
                // Do not operate on whitespace.
                if (!in_array($type, [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
                    switch ($state) {
                        case 'default':
                            if ($type === T_VARIABLE && isset($variable_names[$value])) {
                                // This will be necessary to unset the dumped variable.
                                $parent = &$settings;
                                // This is the current index in parent.
                                $index = $variable_names[$value];
                                // This will be necessary for descending into the array.
                                $current = &$parent[$index];
                                $state = 'candidate_left';
                            }
                            break;
                        case 'candidate_left':
                            if ($value == '[') {
                                $state = 'array_index';
                            }
                            if ($value == '=') {
                                $state = 'candidate_right';
                            }
                            break;
                        case 'array_index':
                            if (_drupal_rewrite_settings_is_array_index($type, $value)) {
                                $index = trim($value, '\'"');
                                $state = 'right_bracket';
                            } else {
                                // $a[foo()] or $a[$bar] or something like that.
                                throw new Exception('invalid array index');
                            }
                            break;
                        case 'right_bracket':
                            if ($value == ']') {
                                if (isset($current[$index])) {
                                    // If the new settings has this index, descend into it.
                                    $parent = &$current;
                                    $current = &$parent[$index];
                                    $state = 'candidate_left';
                                } else {
                                    // Otherwise, jump back to the default state.
                                    $state = 'wait_for_semicolon';
                                }
                            } else {
                                // $a[1 + 2].
                                throw new Exception('] expected');
                            }
                            break;
                        case 'candidate_right':
                            if (_drupal_rewrite_settings_is_simple($type, $value)) {
                                $value = _drupal_rewrite_settings_dump_one($current);
                                // Unsetting $current would not affect $settings at all.
                                unset($parent[$index]);
                                // Skip the semicolon because _drupal_rewrite_settings_dump_one() added one.
                                $state = 'semicolon_skip';
                            } else {
                                $state = 'wait_for_semicolon';
                            }
                            break;
                        case 'wait_for_semicolon':
                            if ($value == ';') {
                                $state = 'default';
                            }
                            break;
                        case 'semicolon_skip':
                            if ($value == ';') {
                                $value = '';
                                $state = 'default';
                            } else {
                                // If the expression was $a = 1 + 2; then we replaced 1 and
                                // the + is unexpected.
                                throw new Exception('Unexpected token after replacing value.');
                            }
                            break;
                    }
                }
                $buffer .= $value;
            }
            foreach ($settings as $name => $setting) {
                $buffer .= _drupal_rewrite_settings_dump($setting, '$' . $name);
            }
    
            // Write the new settings file.
            if (file_put_contents($settings_file, $buffer) === FALSE) {
                throw new Exception(t('Failed to modify %settings. Verify the file permissions.', ['%settings' => $settings_file]));
            } else {
                // In case any $settings variables were written, import them into the
                // Settings singleton.
                if (!empty($settings_settings)) {
                    $old_settings = Settings::getAll();
                    new Settings($settings_settings + $old_settings);
                }
                // The existing settings.php file might have been included already. In
                // case an opcode cache is enabled, the rewritten contents of the file
                // will not be reflected in this process. Ensure to invalidate the file
                // in case an opcode cache is enabled.
                OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $settings_file);
            }
        } else {
            throw new Exception(t('Failed to open %settings. Verify the file permissions.', ['%settings' => $settings_file]));
        }
    }

    Drupal 安装过程中有个配置文件 default.setting.php ,里面存放了默认配置数组,在安装的过程中会让用户在安装界面输入一些配置比如 Mysql 的信息,输入过后此方法通过 file_get_contentstoken_get_all 来获取 setting 中的信息,然后合并用户在页面输入的信息,重新存回文件,因为整个过程涉及到读取文件,更改文件信息,在存入文件,所以 Swoole Compiler 在此处暂时没有更好的解决方案,需要在加密的时候选择不加密 setting 文件。

  • 代码路径: drupal/vendor/symfony/class-loader/ClassCollectionLoader.php:126
    此类中是Symfony读取PHP文件然后作相应处理后缓存到文件中,存在和上面代码同样的问题,暂未找到更好的解决方案

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解 Flask

深入理解 Flask

[美]Jack Stouffer / 苏丹 / 电子工业出版社 / 2016-7-1 / 79.00

Flask 是一种具有平缓学习曲线和庞大社区支持的微框架,利用它可以构建大规模的web应用。学习上手Flask非常轻松,但要深入理解却并不容易。 本书从一个简单的Flask应用开始,通过解决若干实战中的问题,对一系列进阶的话题进行了探讨。书中使用MVC(模型-视图-控制器)架构对示例应用进行了转化重构,以演示如何正确地组织应用代码结构。有了可扩展性强的应用结构之后,接下来的章节使用Flask......一起来看看 《深入理解 Flask》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具