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

в 5:55, , рубрики: javascript, php, информационная безопасность, чистка вирусов, метки: ,

Добрый день, уважаемые читатели!

Данная статья является логическим продолжением этой статьи. В одном из комментариев к ней, оставленном юзером 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> В файле обнаружена искомая строка. 
                                    Cигнатура: <b>" . $founded_str . "</b>
                        </span>
"; 
            }
        }
    }


   /**
     * Функция сканирования вредоносного кода:
     *
     * @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) 
                                        Cигнатура: <b>" . $founded_str . "</b>
                            </span>
";
                }
                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>
";  
                }
                // меняем права в папке в которой находимся обратно на 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>
";                  
                }
                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>
";  
                }
            }
        }
    } 
    
    
   /**
     * Функция удаления БЭКАПОВ файлов
     *
     * @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>
";   
                }
                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>
";  
                }                 
            }
        }
    }                
}

/*
 ----------------------------------------------------------------------------------
 dScaner Class - END
 ----------------------------------------------------------------------------------
*/

?>

Ну и пример использования с базовыми параметрами поиска.

<?
// создаём экземпляр сканера - Дрон
 $dron = new dScaner;

// поиск по файлам из текущей директории
 $dron->find('./', '.');

// очистка файлов
// $dron->scan('./', '.');

// восстановление бэкапов файлов
// $dron->restore_backups('./', '.');

// удаление бэкапов файлов
// $dron->delete_backups('./', '.');
?>

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

Автор: denum

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js