Статьи

Как разоблачить приватные части PHP

Я возился с демпингом PHP-объектов и постоянно сталкивался с кирпичной стеной. Вывод из print_r и friends в некоторых контекстах хорош, но для более крупных структур было бы неплохо немного привести в порядок вывод и обернуть его в некоторый HTML.

Однако эти функции имеют определенные привилегии в том, что они могут обращаться к закрытым / защищенным переменным. Это не то, что можно обойти в простом PHP (за исключением некоторых экзотических расширений , на которые я бы предпочел не полагаться). Кажется, что 5.3.0 может ввести функциональность в Reflection API, но никто не знает, когда выйдет 5.3.0.

Поэтому я понял, что можно сериализовать любой объект в строку, которая будет включать как закрытые, так и защищенные переменные. Все, что мне нужно было сделать, — это затем проанализировать сериализованную строку, и я получил бы эту загадочную информацию. Можно предположить, что такой парсер уже существует. Может быть, это так, но я не смог найти его (хотя я нашел реализацию Java ). Формат довольно простой, поэтому я трачу на него половину воскресного дня. Один очень приятный бонус по сравнению с print_r в том, что формат обрабатывает рекурсию, что очень удобно
для выгрузки вывода в презентабельном виде.

Итак, без лишних слов, вот код:

 < ?php /** * Exports variable information, including private/protected variables and with recursion-protection. * Since this is built upon PHP serialization functionality, unserializable objects may cause trouble. */ class XrayVision { protected $id; function export($object) { $this->id = 1; list($value, $input) = $this->parse(serialize($object)); return $value; } protected function parse($input) { if (substr($input, 0, 2) === 'N;') { return array(array('type' => 'null', 'id' => $this->id++, 'value' => null), substr($input, 2)); } $pos = strpos($input, ':'); $type = substr($input, 0, $pos); $input = substr($input, $pos + 1); switch ($type) { case 's': return $this->s($input); case 'i': return $this->i($input); case 'd': return $this->d($input); case 'b': return $this->b($input); case 'O': return $this->o($input); case 'a': return $this->a($input); case 'r': return $this->r($input); } throw new Exception("Unhandled type '$type'"); } protected function s($input) { $pos = strpos($input, ':'); $length = substr($input, 0, $pos); $input = substr($input, $pos + 1); $value = substr($input, 1, $length); return array(array('type' => 'string', 'id' => $this->id++, 'value' => $value), substr($input, $length + 3)); } protected function i($input) { $pos = strpos($input, ';'); $value = (integer) substr($input, 0, $pos); return array(array('type' => 'integer', 'id' => $this->id++, 'value' => $value), substr($input, $pos + 1)); } protected function d($input) { $pos = strpos($input, ';'); $value = (float) substr($input, 0, $pos); return array(array('type' => 'float', 'id' => $this->id++, 'value' => $value), substr($input, $pos + 1)); } protected function b($input) { $pos = strpos($input, ';'); $value = substr($input, 0, $pos) === '1'; return array(array('type' => 'boolean', 'id' => $this->id++, 'value' => $value), substr($input, $pos + 1)); } protected function r($input) { $pos = strpos($input, ';'); $value = (integer) substr($input, 0, $pos); return array(array('type' => 'recursion', 'id' => $this->id++, 'value' => $value), substr($input, $pos + 1)); } protected function o($input) { $id = $this->id++; $pos = strpos($input, ':'); $name_length = substr($input, 0, $pos); $input = substr($input, $pos + 1); $name = substr($input, 1, $name_length); $input = substr($input, $name_length + 3); $pos = strpos($input, ':'); $length = (int) substr($input, 0, $pos); $input = substr($input, $pos + 2); $values = array(); for ($ii=0; $ii < $length; $ii++) { list($key, $input) = $this->parse($input); $this->id--; list($value, $input) = $this->parse($input); if (substr($key['value'], 0, 3) === " 00* 00") { $values['protected:' . substr($key['value'], 3)] = $value; } elseif ($pos = strrpos($key['value'], " 00")) { $values['private:' . substr($key['value'], $pos + 1)] = $value; } else { $values[str_replace(" 00", ':', $key['value'])] = $value; } } return array( array('type' => 'object', 'id' => $id, 'class' => $name, 'value' => $values), substr($input, 1)); } protected function a($input) { $id = $this->id++; $pos = strpos($input, ':'); $length = (int) substr($input, 0, $pos); $input = substr($input, $pos + 2); $values = array(); for ($ii=0; $ii < $length; $ii++) { list($key, $input) = $this->parse($input); $this->id--; list($value, $input) = $this->parse($input); $values[$key['value']] = $value; } return array( array('type' => 'array', 'id' => $id, 'value' => $values), substr($input, 1)); } } ?> 

Есть как минимум две известные проблемы с этой техникой. Во-первых, ресурсы сериализуются в целые числа. Для дампа это не имеет большого значения, так как ресурс не имеет смысла вне запущенного процесса. Другая проблема __sleep с объектами, которые реализуют __sleep . Поскольку эта функция может иметь побочные эффекты, вы можете испортить вашу программу для объектов, которые используют эту функцию. По моему опыту, в любом случае это редко используемая функциональность, поэтому она меня не особо беспокоит.