内容简介:上个星期碰到个客户使用注:
前言
上个星期碰到个客户使用 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_contents
和 token_get_all
来获取 PHP
文件的类名,但目前未对 Druapl
运行产生影响,可能并未用到其中方法
-
解决方案:
同
StaticReflectionParser类的解决方案一样通过2.1.3版本中的swoole_loader.so扩展中添加函数naloinwenraswwww来获取加密后文件的命名空间和类名
尚未有更好方案的问题:
-
代码路径:
drupal/core/includes/install.inc:220function 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_contents和token_get_all来获取setting中的信息,然后合并用户在页面输入的信息,重新存回文件,因为整个过程涉及到读取文件,更改文件信息,在存入文件,所以Swoole Compiler在此处暂时没有更好的解决方案,需要在加密的时候选择不加密setting文件。 -
代码路径:
drupal/vendor/symfony/class-loader/ClassCollectionLoader.php:126
此类中是Symfony读取PHP文件然后作相应处理后缓存到文件中,存在和上面代码同样的问题,暂未找到更好的解决方案
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming Collective Intelligence
Toby Segaran / O'Reilly Media / 2007-8-26 / USD 39.99
Want to tap the power behind search rankings, product recommendations, social bookmarking, and online matchmaking? This fascinating book demonstrates how you can build Web 2.0 applications to mine the......一起来看看 《Programming Collective Intelligence》 这本书的介绍吧!
SHA 加密
SHA 加密工具
Markdown 在线编辑器
Markdown 在线编辑器