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

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

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

前言

上个星期碰到个客户使用 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文件然后作相应处理后缓存到文件中,存在和上面代码同样的问题,暂未找到更好的解决方案

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

查看所有标签

猜你喜欢:

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

高效团队开发

高效团队开发

[日] 池田尚史、[日] 藤仓和明、[日] 井上史彰 / 严圣逸 / 人民邮电出版社 / 2015-7 / 49.00

本书以团队开发中所必需的工具的导入方法和使用方法为核心,对团队开发的整体结构进行概括性的说明。内容涉及团队开发中发生的问题、版本管理系统、缺陷管理系统、持续集成、持续交付以及回归测试,并且对“为什么用那个工具”“为什么要这样使用”等开发现场常有的问题进行举例说明。 本书适合初次接手开发团队的项目经理,计划开始新项目的项目经理、Scrum Master,以及现有项目中返工、延期问题频发的开发人......一起来看看 《高效团队开发》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

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

在线XML、JSON转换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具