Update: MediaTemple directed me to another blog post with additional details. This highlights another problem with this incident. The information has been spread all over the place. While this blog post does give some good details, it still does not provide cleanup instructions. It simply says that all malicious files have been removed. I’m sorry to say that they have not.
If you have websites hosted on a MediaTemple (gs) then you may have been a victim of this annoying redirect hack, unofficially known as the johnnyA hack. It was once thought to be an issue with WordPress, but in reality static sites are also affected by this. Essentially the attack works by including some encoded JavaScript onto your pages that tries to redirect you to a malicious website or file.
After much searching and with the help of this blog post, I have found the rootkit that is used to do the damage. If you have been hacked you will find some PHP files that were created by the attackers with a bunch of gzipped, base64-encoded source code. I converted that into the actual attack code which is listed below:
1It looks like the attackers simply visit this PHP page and pass in some request parameters to tailor the attack. It looks like there are a few different options such as generating files, executing shell code, etc. I’m no PHP wizard so let me know if you find something interesting.
So how do you find these PHP files? Just SSH into your root directory and run:
1This will list all files that have a string that is longer than 255 characters. This should help you locate these files.
My big question is for MediaTemple. What the hell are you guys doing? Why are we having to dig around for this information? Surely you have determined the same things so why are you not telling people how to clean it up? This official blog post from MediaTemple is vague and tells us nothing about how to actually clean our servers up. Do your job!
I just uploaded a new tutorial that shows you how to provide an HTML5 video fallback option for devices that do not support the Flash Player. The tutorial also shows you how to use the new Flash Media Playback component for quickly adding video to your websites. If getting your video out to the largest number of people is your goal, providing an HTML5 fallback is an absolute necessity.
Tomorrow I’m off to Los Angeles do one of the WebGL BoF talks at Siggraph. I’m very excited to be part of this. My talk will be about the opportunities WebGL has to offer to us as a creative agency.
I’ve been working with WebGL a lot lately. A lot of experimentation but also some client work. The ability to work with hardware accelerated graphics on the web has been inspiring.
I will write more about the conference in a few days. Be sure to give me a shout if you’re in LA as well
I just uploaded a new tutorial that shows you how to create a mobile-optimized website using HTML and CSS. The tutorial also highlights the new multi-screen development features of Dreamweaver CS5. It is important to have a strong grasp on standard web technologies as only then can you make an informed decision about when to use Flash.
I’m a jQuery newbie so I thought I’d try it out in combination with WebGL. It makes a great example together with the GLGE library for WebGL.
Mind you, there are better ways to do tunnels in 3D. This demo is meant to demonstrate how specific things work with GLGE.
An explanation of what’s going on can be found below. Click here to see the live demo or watch the YouTube video if you don’t have a WebGL-enabled browser:
Using jQuery to get the data from Last.fmFirst there’s a bit of jQuery code that gets the xml and the images from Last.fm. I use a php proxy script to circumvent security restrictions.
var images = []; var apiKey = 'e351ab3a5309170fa0390aeaebd09304'; /** * Last.FM Service Calls */ var serviceCalls = [ 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=napalm%20death&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=metallica&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=ambulette&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=dri&api_key=' + apiKey) ]; /** * Start the Last.FM Service Calls */ $(document).ready(function() { loadNextService(); }); /** * Get the artists's top albums */ function loadNextService() { if(serviceCalls.length == 0) { start3D(); return; } var serviceCall = serviceCalls.shift(); /** * Traverse the xml file and get the images */ $.ajax({ type: "GET", url: serviceCall, dataType: "xml", success: function(xml) { $(xml).find('album').each(function(){ $(this).find('image').each(function(){ var id = $(this).attr('size'); if(id == "large") images.push($(this).text()); }) }); loadNextService(); } }); } Creating the tunnel with GLGEI’m going to skip the basic stuff (setting up the scene, adding textures) that I explained in a previous post (here).
There’s one specific thing regarding the textures though. Last.fm usually returns non power of two textures. These can be used with Minefield, but not with Chromium. This resizing is done server-side with a php script:
The planes are grouped within GLGE.Group objects. The syntax for this is very simple:
var currentGroup = new GLGE.Group(); group.addObject(myObject);The rest is self-explanatory:
var images = []; var apiKey = 'e351ab3a5309170fa0390aeaebd09304'; /** * Last.FM Service Calls */ var serviceCalls = [ 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=napalm%20death&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=metallica&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=ambulette&api_key=' + apiKey), 'proxy.php?mimeType=text/xml&url=' + escape('http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=dri&api_key=' + apiKey) ]; /** * Start the Last.FM Service Calls */ $(document).ready(function() { loadNextService(); }); /** * Get the artists's top albums */ function loadNextService() { if(serviceCalls.length == 0) { start3D(); return; } var serviceCall = serviceCalls.shift(); /** * Traverse the xml file and get the images */ $.ajax({ type: "GET", url: serviceCall, dataType: "xml", success: function(xml) { $(xml).find('album').each(function(){ $(this).find('image').each(function(){ var id = $(this).attr('size'); if(id == "large") images.push($(this).text()); }) }); loadNextService(); } }); } /** * Set up the 3D scene */ function start3D() { var doc = new GLGE.Document(); var planeGroups = []; var circleRadius = 5; var degreeStep = 30; var zStep = -11; var firstGroup; var lastGroup; doc.onLoad = function() { var renderer = new GLGE.Renderer(document.getElementById("canvas")); var scene = doc.getElement("mainscene"); camera = scene.getCamera(); renderer.setScene(scene); var planesPerRing = Math.ceil(360/degreeStep); var numPlanes = images.length - (images.length % planesPerRing); var currentGroup = new GLGE.Group(); firstGroup = currentGroup; planeGroups.push(currentGroup); scene.addObject(currentGroup); for(var i=0;i<=numPlanes;i++){ var plane = new GLGE.Object(); plane.setMesh(doc.getElement("Plane")); var material = new GLGE.Material(); var texture = new GLGE.Texture(); var materialLayer = new GLGE.MaterialLayer(); texture.setSrc('ResizeImage.php?size=256&url=' + escape(images[i])); materialLayer.setMapinput(GLGE.UV1); materialLayer.setMapto(GLGE.M_COLOR); materialLayer.setTexture(texture); material.addTexture(texture); material.addMaterialLayer(materialLayer); plane.setMaterial(material); var degrees = (i * degreeStep) % 360; var radians = ( Math.PI / 180 ) * degrees; var degreesTan = ( degrees + 90 ) % 360; var radiansTan = ( Math.PI / 180 ) * degreesTan; plane.setScale(1.3, 5.2, 1.3); plane.setRotX(Math.PI * .5); plane.setRotY(radiansTan); plane.setLocX(Math.cos(radians) * circleRadius); plane.setLocY(Math.sin(radians) * circleRadius); currentGroup.addObject(plane); if(i%planesPerRing==0 && i > 0 && i != numPlanes) { var newGroup = new GLGE.Group(); newGroup.setLocZ(planeGroups.length*zStep); currentGroup.nextGroup = newGroup; currentGroup = newGroup; scene.addObject(currentGroup); planeGroups.push(currentGroup); } } var locXDispl = 0; var rotDispl = 0; function render() { renderer.render(); var group = firstGroup; do { group.setLocZ(group.getLocZ() + 1.5); group = group.nextGroup; } while(group); if(firstGroup.getLocZ() > .5) { firstGroup.setLocZ(lastGroup.getLocZ() + zStep); lastGroup.nextGroup = firstGroup; var nextGroup = firstGroup.nextGroup; firstGroup.nextGroup = null; lastGroup = firstGroup; firstGroup = nextGroup; } var rotDisplSin = Math.sin(rotDispl); camera.setLocX(Math.sin(locXDispl) * 3); camera.setRotY(rotDisplSin * .3); camera.setRotZ(rotDisplSin * .5); locXDispl += .05; rotDispl += .02; } lastGroup = currentGroup; setInterval(render, 1000/60); } doc.load("scene.xml"); }