Добрый день, уважаемые читатели!
Данная статья является логическим продолжением этой статьи. В одном из комментариев к ней, оставленном юзером 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