📜 ⬆️ ⬇️

Cleaning infected site files from malicious code

Good afternoon, dear Habrayuzer!

Some time ago, about a month, a virus appeared on our server. On one of the large projects all * .js files were amazed. The situation is normal - a malicious code was added to the end of the files. Yandex issued a warning about the site being infected, and a task was sent to the technical department to clear it. The situation was resolved quickly, the project was unloaded from the net repository in production, the passwords were changed.

However, soon from all departments of the company in the technical department began to receive complaints about infected sites. Managers complained customers, SEOs trumpeted that sites are losing ground. A real epidemic has begun. In addition to * .js files, * .php files were also infected, at the end of which the code was added:
')
echo 'http://somedomain.com/style.js'; 

The server was exposed to such a large-scale infection for the first time. Thoughts on this matter were different, right up to the backdoor of some disgruntled, dismissed employee who decided to foul. However, this was not confirmed. A shell script was written that cleansing * .php files, and the * .js files were still coped with by unloading from a clean repository. Passwords for accounts of access to sites, access to FTP - everything has been changed. Translated everyone who works with FTP on WinSCP and distributed access key files.

The server slowly began to "recover" and the sites return to Yandex. However, in addition to more than a hundred client sites on our server, there are clients using third-party hosting. Access via FTP, no command line and shell. Almost all sites use self-written CMS (written N-th number of years ago) in conjunction with fckeditor'om or older versions of ckeditor'ov. In the file manager ckfinder'e, the authorization check is implemented by a simple return true; Use not want. It is also worth mentioning that A lot of infected * .js files cannot be cured by unloading a clean repository. Git on such sites is not used by us, and most of the backups on hosting is stored a maximum of 7 days. And since the sites located on third-party hosting, we are practically not monitored, all backups were also infected.

In each file, the virus added an arbitrary number of its own copies, from one to five (I never met again) and they all had different variable names, function names and it was impossible to hook on them. The only constant part of the code of each polymorph was the following code segment:
 =Array.prototype.slice.call(arguments).join(""), 

He was chosen to search for file entries. The Jquery libraries 1.6.3 and 1.7.2 were checked and no matches were found in the source code. So the sequence could be used.

In order not to mess around manually with several dozens of * .js files on each site, it was decided to write a script in php. It must scan all the files specified to it for the search string. To expand the horizons, so to speak, it was decided not to use the exec (), system () commands or, for example, the phpseclib library. The algorithm is simple to disgrace: The script scans all directories starting from the given one, looking for the specified string in the search files. Before making changes, the script backs up the file (well, it’s still not enough) and deletes the line with the required substring. The line work was chosen because the virus was written to the file in one line.

I will give an example of the virus code in the * .js file: pastebin.com/J0zRduQw

I didn’t analyze it; it’s interesting to anyone - there are many examples of parsing obfuscated code on the Internet. Therefore, I will go straight to the scanner code.

 <? /* ---------------------------------------------------------------------------------- dScaner Class - START ---------------------------------------------------------------------------------- */ /* * *  - dScaner         *    * * :   *  : 03-04-2012 *  : 0.0.3 * */ Class dScaner { //      // $get_str -   // $separator -     function request($get_str, $separator) { if (isset($get_str) && !empty($get_str)) { //        $obj = explode($separator, $get_str); return $obj; } else { return false; } } /* * *       : * * $this->find($path, $files_allowed, $requested_string); * * $path -   ,      * $files_allowed -  ,    * $requested_string -   * */ function find($path = './', $files_allowed, $requested_string) { //      ,    $dir_disallow = array('.', '..', '.htaccess', '.git'); 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->find($sub_dir, $files_allowed, $requested_string); } elseif ((is_file($path . $dir)) && (!in_array($dir, $dir_disallow)) && (strpos($dir, $files_allowed) == true) && (strpos($dir, '_BACKUP') == false) ) { //   //      $in_dir_file = $path . $dir; //     $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; } } //      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;'>" . $in_dir_file . " -     .<br> </span><br>"; } } } closedir($temp); } } /* * *    : * * $this->scan($path, $files_allowed, $requested_string); * * $path -   ,      * $files_allowed -  ,    * $requested_string - ,       * */ function scan($path = './', $files_allowed, $requested_string) { //       $dir_disallow = array('.', '..', '.htaccess', '.git'); 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 . '/'; $new_parent_dir = $path . $dir; $this->scan($sub_dir, $files_allowed, $requested_string, $new_parent_dir); } elseif ((is_file($path . $dir)) && (!in_array($dir, $dir_disallow)) && (strpos($dir, $files_allowed) == true) && (strpos($dir, '_BACKUP') == false) ) { //   //      $in_dir_file = $path . $dir; //     $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]); } $str_index++; } //   if ($create_backup) { //              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;'>" . $in_dir_file . " -  . (+ BACKUP) <br> </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;'>".$in_dir_file ." -   . </span><br>"; } //          755 chmod($path, 0755); } } } closedir($temp); } } /* * *     * * $this->restore_backups($path, $files_allowed); * * $path -   ,      * $files_allowed -  ,    * */ function restore_backups($path = './', $files_allowed) { //       $dir_disallow = array('.', '..', '.htaccess', '.git'); 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->restore_backups($sub_dir, $files_allowed); } elseif ((is_file($path . $dir)) && (!in_array($dir, $dir_disallow)) && (strpos($dir, $files_allowed) == true) ) { //   //      $in_dir_file = $path . $dir; 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;'>".$in_dir_file ." - . </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;'>".$in_dir_file ." -  . </span><br>"; } } } } closedir($temp); } } } /* ---------------------------------------------------------------------------------- dScaner Class - END ---------------------------------------------------------------------------------- */ ?> 

The class code is quite detailed, questions should not arise.
Example of use (the first parameter is the start directory of the search, the second is the type of files participating in the search, the third is the search string):

Create a copy of the scanner.

 $dron = new dScaner; 

Before you overwrite something, you should see if there are any files that match the search criteria.

 $dron->find('./', '.js', '=Array.prototype.slice.call(arguments).join(""),'); 

We start the sweep.

 $dron->scan('./', '.js', '=Array.prototype.slice.call(arguments).join(""),'); 

In which case, you can always restore the created backups.

 $dron->restore_backups('./', '.js'); 

The scanner has been tested on many sites and works as it should, the only problem that occurred on our server is the rights of the file owner. It is necessary that the owner of the file is www: www. On average, on one site with several dozen * .js files it took from 5-10 to 20 seconds. And I provide a list of hosts on which the script was successfully tested: infobox, agava, jino, mchost, hc. Of all, the slowest was mchost, for the rest everything worked quite fast.

PS The script does not claim to be a panacea for viruses, it was developed for a specific case of infection and needs to be refined for each subsequent one. However, with the task perfectly copes. I hope someone will be useful.
Best Regards!

Source: https://habr.com/ru/post/141710/


All Articles