Как стать автором
Поиск
Написать публикацию
Обновить

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

Время на прочтение9 мин
Количество просмотров7.9K
Добрый день, уважаемые Хабраюзеры!

Данная статья является логическим продолжением этой статьи. В одном из комментариев к ней, оставленном юзером 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('./', '.');
?>


Вот собственно и всё, чем я хотел с Вами поделиться.
Чистых серверов Вам и стабильной их работы.
Бест регардс!
Теги:
Хабы:
Всего голосов 27: ↑19 и ↓8+11
Комментарии20

Публикации

Ближайшие события