Уязвимости связанная с преобразованием непроверенных сериализованных данных в представление PHP, они же unserialize-баг, все еще актуальны. Более того, проведенное мной исследование встроенных классов PHP показало, что возможны и новые, более универсальные
способы эксплуатации, использующие уязвимости самого интерпретатора.
PHP OBJECT INJECTION
В 2009 году небезызвестный Стефан Эссер на конференции Power of Community в Сеуле выступил с исследованием Shocking News in PHP Exploitation, где, помимо прочего, выявил возможные сценарии атак, когда пользовательские данные попадают в функцию unserialize(). Можно сказать,
что именно тогда появился новый термин PHP Object Injection, обозначающий уязвимость, позволяющую внедрять произвольные объекты PHP в контекст веб-приложения. Уязвимость позволяет проводить множество атак: от XSS до выполнения произвольного кода, в зависимости от структуры объектов уязвимого приложения.
Сериализованные данные после преобразования в объект PHP могут изменить рабочий процесс приложения с помощью так называемых магических методов. Например, если у класса определен магический метод __get(), то он будет вызываться каждый раз, когда у объекта запрашивается несуществующее свойство. При эксплуатации PHP Object Injection наиболее полезны методы __wakeup() и __destruct(): первый вызывается при десериализации объекта, второй — при выгрузке объекта, то есть при завершении работы скрипта. Оба метода вызываются автоматически, без необходимости проводить какие-либо операции над объектом.
В сложных современных веб-приложениях без объектноориентированного подхода просто не обойтись, поэтому очень часто можно встретить деструкторы классов, которые выполняют действия для выгрузки объекта. Например, класс взаимодействия с базой данных закрывает соединение, класс кеша может удалять временные файлы. Именно в деструкторах таится главная опасность для разработчиков, которые зачастую не учитывают, что при десериализации возможна подмена свойств объекта на произвольные значения. Многие популярные веб-приложения были затронуты
PHP Object Injection, в их числе Invision Power Board, Joomla и vBulletin.
В последней версии vBulletin я обнаружил любопытный unserialize(). Он показал, что полезными могут оказаться не только __wakeup() и __destruct(), все зависит от того, какие данные ожидает веб-приложение после десериализации данных. И более того, необязательно ориентироваться только на собственные классы приложения, ведь есть и внутренние классы PHP, речь о которых пойдет ниже.
VBULLETIN
В плане безопасности популярный форум vBulletin знал лучшие годы. В пятой версии «вобла» еще дальше ушла от простого форума и стала громоздким комьюнити-комбайном. Ради праздного интереса я открыл папку core (которая, к слову, не защищена .htaccess), где в инклудах обнаружил исходники Apache log4php. Данный фреймворк — удобный инструмент логирования в PHP и используется в таких проектах, как SugarCRM, vtiger и CMS Made Simple. Но если в этих веб-приложениях от log4php остался только основной функционал, то в vBulletin обнаружился полный код фреймворка, в том числе доступная из веба папка с примерами использования логфреймворка. Один из скриптов оказался крайне любопытным: при обращении к нему на порту
4242 запускался сервер, который при получении данных пытался их десериализовать. Так как полезных магических методов в log4php найти не удалось, было решено обратиться к классам PHP. С помощью следующего скрипта я получил список всех магических методов в объявленных классах:
<?php $classes = get_declared_classes(); foreach($classes as $class) { $methods = get_class_methods($class); foreach ($methods as $method) { if (in_array($method, array('__destruct', '__toString', '__wakeup', '__call', '__callStatic', '__get', '__set', '__isset', '__unset', '__invoke', '__set_state'))) { print $class . '::' . $method . "n"; } } }
Список получился объемным, но, как оказалось, почти все классы не позволяли сериализацию либо были бесполезными. Внимание привлек класс SoapClient и его метод __call(), который вызывается, если попытаться вызвать несуществующий метод. Это как раз то, что нужно, ведь в уязвимом скрипте после преобразования данных в объект вызывался метод getRootLogger(). Класс SoapClient имеет множество свойств, а при вызове несуществующего метода он позволяет отправлять SOAP-запросы на произвольные адреса. Уже интересно, посмотрим, что можно из этого выжать.
SOAPCLIENT
Конструктор класса SoapClient принимает два аргумента: адрес WSDL-документа в формате XML, описывающий SOAP интерфейс, а также массив опций. Если передать в качестве $wsdl значение NULL, то объект будет инициализирован в non-WSDL режиме. Проблема в том, что при режиме с WSDL-документом класс не предусматривает нормальной сериализации свойств, а вот с non-WSDL все в порядке. Поэтому остается один вариант с $wsdl=null и ограниченным набором опций. Но даже с тем, что было доступно, получилось довольно много. Начнем с банальной XSS’ки, конструируем следующий объект SoapClient:
<?php $c = new SoapClient(null, array('uri'=>'http:// test.com/', 'location'=> 'http://test.com/api.php'));
Контролируемый нами скрипт api.php отдает HTTP-ответ
с кодом 404:
<?php header("HTTP/1.0 404 <script>alert(1)</script>");
Вместо статус-сообщения «Not Found» api.php отправляет произвольную строку, SoapClient видит код 404, генерирует исключение SoapFault, сообщая о том, что удаленный ресурс не найден, и возвращает нашу строку в тело ответа.
Одна из возможностей SoapClient — локальное кеширование WSDL-документа. Хотя это не помогло бы эксплуатации PHP Object Injection в vBulletin, все же стало интересно, как это делает SoapClient. Оказалось, что документ сохраняется без всяких проверок соответствия пути директиве конфигурации PHP open_basedir, которая запрещает файловые операции вне указанного каталога:
<?php ini_set('open_basedir', '/var/www'); ini_set('soap.wsdl_cache_enabled', true); ini_set('soap.wsdl_cache_dir', '/tmp'); $c = new SoapClient('http://test.com/test.wsdl', array('cache_wsdl' => WSDL_CACHE_DISK));
Итак, нужно было что-то более интересное, чем просто XSS. Почему бы не XXE, ведь SOAP работает с XML? И действительно, SoapClient был уязвим перед внедрением внешних XML-сущностей, даже несмотря на то, что при попытке парсинга DOCTYPE сообщал, что он не поддерживается! Дело в том, что исключение вызывалось уже после обработки DTD, а в используемом XML-парсере LibXML опция обработки внешних сущностей включена по умолчанию. Никакого вывода сущностей добиться не удалось, однако помогло замечательное исследование реализации XXE через внешние каналы связи моих коллег Алексея Осипова и Тимура Юнусова. Техника позволяет отправлять содержимое файла через HTTP-запросы прямо в запрашиваемом пути. А вместе с разнообразными PHP-обработчиками схем удалось добиться чтения любых файлов.
В этом помог враппер php:// и фильтр base64:
php://fi lter/read=convert.base64-encode/resource=/etc/passwd
Итак, эксплуатация PHP Object Injection через внутренний класс PHP оказалась успешной. Помимо vBulletin, данный вектор будет работать в Joomla <=3.0.3, где через unserialize() возможна SQL-инъекция и удаление произвольной директории.
Вполне очевидно, что не всегда можно встретить вызов метода на десериализованном объекте, и, к сожалению, в классах PHP нет ни одного деструктора, который брал бы из свойства объект и пытался выполнить у него какой-либо метод. Если обратиться к популярным компонентам, то одним из самых подходящих будет шаблонизатор Smarty. В нем можно встретить следующий код:
<?php public function __destruct() { if ($this->smarty->cache_locking &&isset($this->cached) && $this->cached->is_locked) { $this->cached->handler->releaseLock($this->smarty, $this->cached); } }
Если поместить в свойство handler наш объект SoapClient, то операции над объектом в коде веб-приложения не потребуются и XXE будет проэксплуатирована автоматически через метод __destruct().
WHAT’S NEXT?
В PHP 5.5 намечается нововведение в функции unserialize(), а именно второй аргумент, который позволит разработчикам запретить обработку объектов либо указать белый список разрешенных. В настоящее время используются регулярные выражения, которые зачастую можно легко обойти, либо данные не проверяются вовсе. Например, в Invision Power Board <= 3.3.4 была вот такая смешная проверка:
$_value = $_COOKIE[ ipsRegistry::$settings['cookie_id'].$name ]; if ( substr( $_value, 0, 2 ) == 'a:' ) { return unserialize( stripslashes( urldecode( $_value ) ) ); }
В строке проверяются первые два символа: если это «a:», то есть сериализованный массив, то строка попадает в unserialize(). Однако атакующему ничто не мешает отправить массив объектов, что приведет к обходу проверки.
Говоря о будущем PHP Object Injection, стоит рассказать о PHP-фреймворке следующего поколения под именем Phalcon, который становится все более популярным у вебразработчиков. Весь его код написан на чистом C, что делает его самым быстрым PHP-фреймворком. Phalcon реализован в качестве расширения PHP, соответственно, требует компиляции и включения в конфигурацию PHP. При этом все классы фреймворка становятся доступными в контексте веб-приложения без каких-либо инклудов внешних файлов. Если представить shared-хостинг, у которого Phalcon включен для всех пользователей по умолчанию, то очевидно, что это позволит использовать классы фреймворка через unserialize(), даже если уязвимое веб-приложение написано не на его базе. Я решил проверить, возможна ли эксплуатация unserialize() через классы Phalcon, и оказалось — очень даже просто!
PHALCON
Получив магические методы из всех классов фреймворка, я не нашел ни одного деструктора, но обнаружил один__wakeup():
<?php PHP_METHOD(Phalcon_Logger_Adapter_File, __wakeup){ zval *path, *options, *mode = NULL, *fi le_handler; PHALCON_MM_GROW(); PHALCON_OBS_VAR(path); phalcon_read_property_this(&path, this_ptr, SL("_path"), PH_NOISY_CC); PHALCON_OBS_VAR(options); phalcon_read_property_this(&options,this_ptr, SL("_options"), PH_NOISY_CC); if (phalcon_array_isset_string(options,SS("mode"))) { PHALCON_OBS_VAR(mode); phalcon_array_fetch_string(&mode, options,SL("mode"), PH_NOISY_CC); } else { PHALCON_INIT_NVAR(mode); ZVAL_STRING(mode, "ab", 1); } // Re-open the fi le handler if the logger was // serialized PHALCON_INIT_VAR(fi le_handler); PHALCON_CALL_FUNC_PARAMS_2(fi le_handler,"fopen", path, mode); phalcon_update_property_this(this_ptr,SL("_fi leHandler"), fi le_handler TSRMLS_CC); PHALCON_MM_RESTORE(); }
Данный код получает из protected-свойства _path путь и открывает его функцией fopen() в режиме, передающемся в свойстве _options. С помощью данного кода можно открыть тысячи файлов и забить все дескрипторы, но это неинтересно. Но что, если поместить в _path объект? При вызове fopen() он будет преобразован в строку, за что отвечает магический метод __toString(). А вот этот метод Phalcon использует очень широко. Я не буду описывать все внутренности фреймворка, скажу
лишь, что в одном из __toString() удалось добиться инициализации произвольных объектов и вызова любых методов. Уже на этом этапе можно подсунуть наш SoapClient и провести XXE, но RCE всегда лучше! Пробежавшись по исходникам на предмет вызова функции phalcon_require (инклудит файлы так же, как и require в PHP), я обнаружил класс PhalconMvcViewEnginePhp, в котором метод render позволяет инклудить произвольные файлы. Таким образом, через __wakeup() удалось вызвать __toString, а через него — подключение произвольных файлов.
PHP Object Injection все еще жив и будет жить. Вместе с «безопасной» версией unserialize() в PHP 5.5 появится много новыхклассов, и, возможно, не без уязвимостей. Так что смотрим в будущее!
Ссылки:
PHP Object Injection на OWASP: bit.ly/15vbvAW
Shocking News in PHP Exploitation: bit.ly/11mcvHy
Магические методы PHP: bit.ly/172anb
Apache log4php: bit.ly/aF7jHG
XXE Out-of-Band Data Retrieval: bit.ly/12pyfWb
1 comments On Как эксплуатировать уязвимость PHP OBJECT INJECTION
Pingback: DDOS атака на сайт - инструменты и технологии - Cryptoworld ()