Очистка заражённых файлов сайта от вредоносного кода. Продолжение

    Добрый день, уважаемые Хабраюзеры!

    Данная статья является логическим продолжением этой статьи. В одном из комментариев к ней, оставленном юзером Agel_Nash была указана новая сигнатура вируса. Файлы подверженные атаке — *.js.

    Сразу несколько сайтов наших клиентов, которые не последовали нашим рекомендациям сменить доступы фтп, почистить свои машины от зловредов и сменить фтп клиенты, подверглись заражению данным вирусом. В файл *.js прописывается следующий код: pastebin.com/2PWJycAd. Размещается он в одну строку и строго в конец файла.

    С помощью нехитрых манипуляций код был деобфусцирован. Для тех кому интересно, выкладываю читабельный вид: данного кода (подобный код уже указывал MrMYSTIC).
    Деобфускацию проводил простешим способом. Зашёл на jsfiddle и вместо eval вывел на страницу генерируемый код. Делает он несложные манипуляции, а именно следующее: Генерирует путь до файла *.js используя url = 305yoy.bdcfwpndqm.is-a-therapist.com/g/, после чего собирает элемент script с полученным путём и добавляет его в head страницы, на которой отработал.

    Для очистки сайтов я использовал сканер, про который я писал в прошлой статье. Однако решил немного его «причесать», учитывая замечания в комментариях. Отдельное спасибо за конструктивную критику charon и pro100tak (поставил бы + если бы мог голосовать).

    Алгоритм старый, работает построчно в файлах. Поэтому нужно было выявить, за что зацепиться сканеру.

    Взял за основу указанный Agel_Nash, однако сканер показал неожиданно небольшое количество заражённых файлов. Странно, ведь пусть и не тысячи, но сотни *.js файлов в проекте точно есть. Стал копать и верно, доверяй но проверяй. Выбранная сигнатура встречалась не везде в виду того, что она не конечна. Выборочно проверив несколько файлов в различной удалённости друг от друга дирректориях, была выявлена пара вариантов:
    )try{Boolean().prototype.q}catch(egewgsd){f=[
    

    и
    )try{Date().prototype.q}catch(egewgsd){f=[
    

    Разница небольшая и состоит в двух словах:
    Date() и Boolean()
    

    Предположительно, таким образом могут перебираться все типы данных: даты, числа, строки и прочее. Хорошо, значит берём оставшуюся часть строки, а именно:
    ().prototype.q}catch(egewgsd){f=[
    

    На сей раз сканер показал все файлы, которые были заражены данным вирусом. То что нужно! Осталось проверить Jquery, fancybox и ещё несколько используемых библиотек. Чисто… Вхождений в их исходном коде не обнаружено. Отлично, можно использовать.

    Немного переработал сканер и запустил. Он вновь отработал как и задумано, не подвёл.

    Изменения:
    — начал собирать список сигнатур (на данный момент их две, можно запускать сканер без параметра искомой строки);
    — указывает какую из сигнатур обнаружил в файле;
    — добавлена функция удаления бэкапов (delete_backups());
    — вызов функций можно осуществлять без параметров (в таком случае используются заданные по умолчанию):
       $dron->find();
       $dron->scan();
       $dron->restore_backups();
       $dron->delete_backups();
    

    — добавлен функционал смены владельца директории на время работы с ней сканером, но этот участок я закомментировал. Не довелось протестировать:
                            /*
                            // получаем имя владельца директории  
                            $unformated_path_stat = stat($path);
                            $path_stat = posix_getpwuid($unformated_path_stat['uid']);
                            $path_user_name = $path_stat['name'];                                                
                            // меняем имя владельца директории
                            chown($path, 'www');
                            */
                 
                            ...
    
                            // восстанавливаем имя владельца директории
                            // chown($path, $path_user_name); 
    

    — добавлена функция dir_content() которая собирает все файлы в массив, который в последствии используеся всеми остальными функциями;
    — при сканировании игнорирует самого себя.

    Привожу код обновлённого сканера, с подробными комментариями:
    <?
    /*
     ----------------------------------------------------------------------------------
     dScaner Class - START
     ----------------------------------------------------------------------------------
    */
    /**
    *   @param Имя: dScaner
    *   @param Описание: Класс для сканирования директорий на наличие вредоносного кода в 
    *   указанных типах файлов   
    *   
    *   @param Разработчик: Денис Ушаков
    *   @param Версия разработки: 0.0.5 (13-04-2012)
    *
    */
    Class dScaner {
    
        // список файлов
        private $arr_files = array();
        // список сигнатур для поиска
        public $signatures = array(     
                                    '=Array.prototype.slice.call(arguments).join(""),',
                                    '().prototype.q}catch(egewgsd){f=['  
                                  );
    
       /**
         * Преобразуем входной параметр в массив 
         *
         * @param string $get_str Список параметров
         * @param string $separator Разделитель параметров в списке
         * @return array - массив параметров или FALSE
         */
        function request($get_str, $separator)
        {
            if (!empty($get_str))
            {   
                // эксплоадим строку в массив и возвращаем его
                $obj = explode($separator, $get_str);
                return $obj;
            }
            else
            {
                return false;
            }
        }
    
    
       /**
         * Функция получения списка файлов указанного расширения
         *
         * @param string $path - путь до директории, от которой отталкиваться при сканировании
         * @param string $files_allowed - список файлов, которые подвергаются сканированию
         */
        function dir_content($path = './', $files_allowed = '.')
        {
            // исключаемые ссылки на директории и файлы, которые будут игнорироваться
            $dir_disallow = array('.', '..', '.htaccess', '.git', 'zlordwaters');
    
            if(is_dir($path))
            {
                $temp = opendir($path);
                while (false !== ($dir = readdir($temp))) 
                {
                    if ((is_dir($path . $dir)) && 
                        (!in_array($dir, $dir_disallow)) ) 
                    {
                        // если директория - сканируем её
                        $sub_dir = $path . $dir . '/';
                        $this->dir_content($sub_dir, $files_allowed);
                    } 
                    elseif ((is_file($path . $dir)) && 
                            (!in_array($dir, $dir_disallow)) && 
                            (strpos($dir, $files_allowed) == true) &&
                            (strpos($dir, '_BACKUP') == false) && 
                            (strpos($dir, trim($_SERVER['SCRIPT_NAME'], '/')) === false) )
                    {
                        // Если файл, то собираем массив файлов
                        $this->arr_files[] = $path . $dir;
                    }      
                }
                closedir($temp);
            }
        }
    
    
       /**
         * Функция поиска в файлах вхождения заданной строки:
         *
         * @param string $path - путь до директории, от которой отталкиваться при сканировании
         * @param string $files_allowed - список файлов, которые подвергаются сканированию
         * @param string $requested_string - строка поиска
         */
        function find($path = './', $files_allowed = '.', $requested_string = '().prototype.q}catch(egewgsd){f=[')
        {
            // получаем список файлов нужного расширения
            $this->dir_content($path, $files_allowed);        
    
            foreach($this->arr_files AS $in_dir_file)
            {
                // считываем файл в строку
                $temporary_file = file_get_contents($in_dir_file);  
                // флаг найденного вхождения искомой строки
                $file_founded = false;
    
                // разбиваем файл на строки
                $tf_strings = explode("\n", $temporary_file);
                // обрабатываем каждую отдельно
                foreach ($tf_strings AS $item)
                {
                    // проверяем на заданную строку
                    $item = strval($item);
                    if (strpos($item, $requested_string) !== false)
                    { 
                        $file_founded = true;
                        $founded_str = $requested_string;
                    }
    
                    // проверяем по имеющимся сигнатурам
                    foreach ($this->signatures AS $signa)
                    {
                        $signa = strval($signa);
                        if (strpos($item, $signa) !== false)
                        { 
                            $file_founded = true;
                            $founded_str = $signa;
                        }
                    }
                }
                // если в файле найдена строка
                if ($file_founded)
                {
                    // выводим путь до файла в котором найдено вхождение
                    print "<span style='display:block; 
                                        padding:5px; 
                                        border:1px solid #1f4f18;
                                        background-color:#d5f5ce; 
                                        font-size:12px;
                                        line-height:16px;
                                        font-family:tahoma, sans-serif;
                                        margin-bottom:-15px;'><h3>" . $in_dir_file . "</h3> В файле обнаружена искомая строка. <br>
                                        Cигнатура: <b>" . $founded_str . "</b>
                            </span><br>"; 
                }
            }
        }
    
    
       /**
         * Функция сканирования вредоносного кода:
         *
         * @param string $path - путь до директории, от которой отталкиваться при сканировании
         * @param string $files_allowed - список файлов, которые подвергаются сканированию
         * @param string $requested_string - строка, по которой определяется наличие вредоносного кода
         */
        function scan($path = './', $files_allowed = '.', $requested_string = '().prototype.q}catch(egewgsd){f=[')
        {
            // получаем список файлов нужного расширения
            $this->dir_content($path, $files_allowed);
            
            foreach($this->arr_files AS $in_dir_file)
            {
                // считываем файл в строку
                $temporary_file = file_get_contents($in_dir_file);  
                // флаг бекапа файла                                   
                $create_backup = false;                    
    
                // разбиваем файл на строки и считываем каждую отдельно
                $tf_strings = explode("\n", $temporary_file);
                // индекс строки файла
                $str_index = 0;
                // каждую строку обрабатываем отдельно
    
                foreach ($tf_strings AS $item)
                {
                    // проверяем на заданную строку
                    $item = strval($item);
                    if (strpos($item, $requested_string) !== false)
                    { 
                        // если в строке есть вхождения искомого запроса
                        // флаг бекапа файла, в котором найден вредоносный код
                        $create_backup = true; 
                        // удаляем всю строку с вредоносным кодом
                        unset($tf_strings[$str_index]);
                        $founded_str = $requested_string;
                    }
                    
                    // проверяем по имеющимся сигнатурам
                    foreach ($this->signatures AS $signa)
                    {
                        $signa = strval($signa);
                        if (strpos($item, $signa) !== false)
                        { 
                            // если в строке есть вхождения искомого запроса
                            // флаг бекапа файла, в котором найден вредоносный код
                            $create_backup = true; 
                            // удаляем всю строку с вредоносным кодом
                            unset($tf_strings[$str_index]);
                            $founded_str = $signa;
                        }
                    }
                    // переходим на следующую строку
                    $str_index++;
                }
    
                // создаём бэкап
                if ($create_backup)
                {
                    /*
                    // получаем имя владельца директории  
                    $unformated_path_stat = stat($path);
                    $path_stat = posix_getpwuid($unformated_path_stat['uid']);
                    $path_user_name = $path_stat['name'];                                                
                    // меняем имя владельца директории
                    chown($path, 'www');
                    */
                    // меняем права в папке в которой находимся чтобы иметь возможность писать в неё
                    chmod($path, 0777);
                    // формируем имя БЭКАПа файла
                    $temp_file_backup = $in_dir_file.'_BACKUP';
                    // сохраняем БЭКАП файла рядом с исходным
                    file_put_contents($temp_file_backup, $temporary_file);
                    // собираем очищенный файл в строку
                    $scanned_file = implode("\n", $tf_strings);
                    // сохраняем очищенный файл
                    if (file_put_contents($in_dir_file, $scanned_file))
                    {   
                        // перезаписали удачно
                        print "<span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #1f4f18;
                                            background-color:#d5f5ce; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>" . $in_dir_file . "</h3> Файл очищен. (+ BACKUP) <br>
                                            Cигнатура: <b>" . $founded_str . "</b>
                                </span><br>";
                    }
                    else
                    {
                        // перезапись не удалась
                        print "<span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #822121;
                                            background-color:#ea7575; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>" . $in_dir_file . "</h3> Файл НЕ очищен.
                                            Cигнатура: <b>" . $founded_str . "</b>
                                </span><br>";  
                    }
                    // меняем права в папке в которой находимся обратно на 755
                    chmod($path, 0755); 
                    // восстанавливаем имя владельца директории
                    // chown($path, $path_user_name);                                              
                }
            }
        }
    
    
       /**
         * Функция восстановления БЭКАПОВ файлов
         *
         * @param string $path - путь до директории, от которой отталкиваться при восстановлении
         * @param string $files_allowed - список файлов, которые подвергаются восстановлению
         */
        function restore_backups($path = './', $files_allowed = '.')
        {
            // получаем список файлов нужного расширения
            $this->dir_content($path, $files_allowed);
            
            foreach($this->arr_files AS $in_dir_file)
            {
                if (is_file($in_dir_file.'_BACKUP'))
                {
                    // БЭКАП существует, получаем его содержимое
                    $temporary_file_from_backup = file_get_contents($in_dir_file.'_BACKUP');
                    // восстанавливаем бэкап файла
                    if (file_put_contents($in_dir_file, $temporary_file_from_backup))
                    {   
                        // удаляем бэкап
                        unlink($_SERVER['DOCUMENT_ROOT'].'/'.$in_dir_file.'_BACKUP');
                        // бэкап восстановили
                        print "<span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #1f4f18;
                                            background-color:#d5f5ce; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>".$in_dir_file ."</h3> Восстановлен.
                                </span><br>";                  
                    }
                    else
                    {
                        // бэкап НЕ восстановили
                        print "<span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #822121;
                                            background-color:#ea7575; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>".$in_dir_file ."</h3> НЕ восстановлен.
                                </span><br>";  
                    }
                }
            }
        } 
        
        
       /**
         * Функция удаления БЭКАПОВ файлов
         *
         * @param string $path - путь до директории, от которой отталкиваться при удалении бэкапов
         * @param string $files_allowed - список файлов, которые подвергаются удалению
         */
        function delete_backups($path = './', $files_allowed = '.')
        {
            // получаем список файлов нужного расширения
            $this->dir_content($path, $files_allowed);
            
            foreach($this->arr_files AS $in_dir_file)
            {
                if (is_file($in_dir_file.'_BACKUP'))
                {
                    // БЭКАП существует, удаляем его
                    if (unlink($_SERVER['DOCUMENT_ROOT'].'/'.$in_dir_file.'_BACKUP'))
                    {
                        print " <span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #1f4f18;
                                            background-color:#d5f5ce; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>".$in_dir_file ."_BACKUP</h3> Удалён.
                                </span><br>";   
                    }
                    else
                    {
                        // бэкап НЕ удалили
                        print "<span style='display:block; 
                                            padding:5px; 
                                            border:1px solid #822121;
                                            background-color:#ea7575; 
                                            font-size:12px;
                                            line-height:16px;
                                            font-family:tahoma, sans-serif;
                                            margin-bottom:-15px;'><h3>".$in_dir_file ."_BACKUP</h3> НЕ удалён.
                                </span><br>";  
                    }                 
                }
            }
        }                
    }
    
    /*
     ----------------------------------------------------------------------------------
     dScaner Class - END
     ----------------------------------------------------------------------------------
    */
    
    ?>
    

    Ну и пример использования с базовыми параметрами поиска.
    <?
    // создаём экземпляр сканера - Дрон
     $dron = new dScaner;
    
    // поиск по файлам из текущей директории
     $dron->find('./', '.');
    
    // очистка файлов
    // $dron->scan('./', '.');
    
    // восстановление бэкапов файлов
    // $dron->restore_backups('./', '.');
    
    // удаление бэкапов файлов
    // $dron->delete_backups('./', '.');
    ?>
    


    Вот собственно и всё, чем я хотел с Вами поделиться.
    Чистых серверов Вам и стабильной их работы.
    Бест регардс!

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 20

      0
      Благодарю! Обязательно воспользуюсь.
        0
        Если бы класс умел работать с базами какого-нибудь антивируса, то он был бы во много раз эффективнее, так как в базы антивирусов подобные javascript вирусы попадают гораздо быстрее и там уже большое количество сигнатур.
          0
          а что если вирус внедрится в середину строки?
            0
            Нужно будет прорабатывать более детально этот момент. Использовать регулярные выражения, прорабатывая маску общей сигнатуры. Как один из вариантов.
            0
            не понимаю зачем такую простыню писать, когда на баше можно парой комманд обойтись: habrahabr.ru/post/139510/?
              0
              Просто понимаете, не везде есть возможность использовать консоль.
                0
                вы все руками делаете? например данный скрипт можно повесить в крон и забыть, вот сигнатуры конечно лучше, действительно брать из публичных.
                  0
                  Ну да, руками. Если уж заразился, надо не только вылечиться, но и выяснить, почему заразился и устранить причину, а не латать дырку в фоновом режиме.
                    0
                    лечить, согласен, необходимо, но иногда это вина плечах хостера, а тех поддержа долго откликается, это лечиться сменой хостера, но тем не менее =)
                0
                Спасибо за код. Столкнулся вчера с данной проблемой. Скрипт этот действительно помог, теперь антивирусы не ругаются, зайдя на мой проект.
                Только вот интересно узнать — как вирус внедряется туда?
                  0
                  Чтобы внедрить код в любой файл, например, можно использовать данный сканер. В любое место файла, куда захочется. Было бы желание. Но для этого нужен доступ по фтп или возможность залить скрипт на сайт. Так что всё упирается в уязвимости.
                    0
                    Вот в том то и вопрос — каким образом. Чтобы была возможность закрыть эту дырку. Потому что, вполне возможно, что через эту дыру скрипт опять попадет на мой проект. На всякий случай пароли к ФТП сменил, но я работаю с проектом через линукс, не должно же вроде утекать ничего с подобных машин. Хотя, возможно, я и ошибаюсь.
                      0
                      fckeditor или другие публичные скрипты стоят? Обычно в них находят дыры и сразу под угрозой оказываются сотни сайтов.
                  0
                  Попробуйте скрипт айболит revisium.com/ai/, он ищет шеллы, дорвеи и вирусы. В том числе и в javascript.
                  • UFO just landed and posted this here
                      0
                      а что вы будете делать, если на виртуальном хостинге нет консоли и поддержки ssh?
                      всё равно нужно будет использовать php — exec, system или phpseclib.
                      а на баше (до работы с заразой на PHP) мы использовали вот этот скрипт:

                      for i in `find . -name "index.php";find . -name "index.html"`
                      do
                      echo $i
                      mv $i $i-old
                      cat $i-old | sed "s/bip-count\.info//g" > $i
                      done;
                      
                      • UFO just landed and posted this here
                          0
                          На баше скрипт писался не под iframе'ы. Расширить изменением регулярки с условиями поиска — можно всегда. Тогда чистить будет всё что угодно, вы же сами понимаете.

                          Удаление всей строки не то что, не совсем удачно в случае, если вредоносный код в середине или начале строки. Такой подход в корне неудачен, если подходить к решению проблемы с таким кодом подобным образом.
                          Я же указал, что код добавлялся строго в конец файла и в одну строку. Именно поэтому был выбран подобный вариант удаления — строчный. Для других вариаций заражения необходимо менять поиск вхождений, используя например preg_match() или preg_replace().

                          А вот по поводу «фокуса» с флагами sed'a, спасибо, возьму на заметку.
                          • UFO just landed and posted this here
                    • UFO just landed and posted this here

                      Only users with full accounts can post comments. Log in, please.