Friday, July 27, 2012

Judge a Book by Its Cover

Last week Dana Goldberg posted this piece.  Sylvie, Juniper and I were outside of Modern Times Bookstore on our way to picking up vegetables for dinner and I thought I'd replicate the experiment, for science sake.

Here are the findings:

Wheels of Life by Anodea Judith

Juniper: It has a bad cover.
Me: Why?  What's wrong with it?
J: She looks burned up.

The Fountainhead by Ayn Rand

Me: What do you think this one's about?
J: A man.
Sylvie: A man.
J: And a ball.  [opening cover] And words.  It's about words.

A Million Little Pieces by James Frey

S: Mmmm . . . a ball!
J: No, it's about cupcake making.

Memoirs of a Geisha by Arthur Golden

J: That looks like Mamu.

Rhapsody by Elizabth Haydon

Me: Okay, tell me what happens in this book.
J: Fighting!
Me: What kind of fighting?
J: Uhh, fighting like when you tackle each other and wrestle on each other.
Me: What about these people?  What can you tell me about these people?
J: They look not very nice at all.

Banishing Verona by Margot Livesey

J: It's about people having babies.

Labels: , , ,

Friday, March 02, 2012

The PHP Worm and Encrypted Webshells: Dyslexic Mayans Want to Sell You Cialis

Problems with preg_replace("/.*/e","\x65\x76 , $_8b7b="\x63\x72\, eval(base64_ hacks, and those damn domain redirects

My shared host account got hacked-TFU last Monday. After looking a little closer it became clear that there had been more than one break in. Apparently my account also got hacked last November, three times in December (twice by the same hacker!) and once in January too, so for the past few months my server has had a back door like Dennis the Menace's hanging pajama flap.

Monday's attack was the one that finally got my attention because its impact was so widespread; this particular worm grabs every php script it can get a hold of and attaches a long eval(base64_decode statement to the header. The damage was spread across three separate domains and corrupted every php file in my Gallery2, PHPBB, Joomla and Wiki installations. Only seven files were left untouched; five of those had been placed in my domain tree by other hackers, and the other two were the entry point through which the worm script got executed. A considerate and genteel group, these guys, that in the course of clobbering my site the hackers they were nice enough not to step on each others' toes.

So, what does eval(base64 code do? It adds this extra line of javascript to each page:

 <script src=""></script> 

And here is the offending php code that got meanly inserted throughout my account:

The code is an illegible jumble because it's encoded in base64. You can see an unjumbled version in your browser by changing the first "eval" to "print".

Here is a base64 decoded version, with my comments as to what the worm does:

Cleaning the Mess

The first thing to do: to button up your pajamas. Here's how you find your open, world writeable directories, and to document those in a file:

find . -type d -perm -o=w > openbackdoors.txt

Run that command in your home directory. You'll want the file for later reference; it can be viewed with:

more ~/openbackdoors.txt

Then close those directories to world-write access:

find . -type d -perm -o=w -print -exec chmod 770 {} \;

Using your openbackdoors.txt log, go back and scrutinize *all* the files in those directories. I found three types of POST injection points; two were different obfuscations of the WSO webshell, and another looked to be a less sophisticated access portal. Even after changing directory permissions those files leave the whole system accessible and vulnerable, a sort of backdoor doorstop if you will.

Here is the first version of the webshell:

And here is the second version:

What Are We Dealing with Here?

I want to take a moment to discuss decrypting these files and what they do.  If you're primarily interested in fixing your system and taking remedial action, skip down to And How Do We Deal with It? below.

So, it turns out these two files are functionally the same – but then why do they look so different? The first is obfuscated in a combination of binary and base64 encoding, and gzdeflate() data compression. If you were to replace the preg_replace() function with print, then remove the first, and third argument, and everything between the single-quotes of the second argument, you'd get this expression:


which when print()'ed to the browser displays:


The second webshell script is not at all encrypted; it is, however, cutely disguised by comments to pass as friendly code written for RSS management. It takes a low hacker indeed to besmirch the honorable name of Kellan Elliot-McCrea.

Verifying that the two scripts are the same thing is easy enough. Do this by commenting out that first line (" $auth_pass = etc. . ." ), which is the key for a password-wrapper around the shell. Remove the password requirement, fire up the script in your browser and -- voila! -- the whole domain tree will now do your bidding. Oh, and how do I know I got hacked by the same person multiple times? Because they keep using the same password!

The third backdoor door stop was the most challenging for me to figure out. It looks like this:

Two crucial things to know here are that the odd looking string of slashes, x'es and digits is binary code, and that binary code once served to the browser appears the same as regular typescript. Since binary code doesn't need to be unencoded and can be printed out, you can simply comment out the final expression and add:

echo $_8b7b;

to the bottom see what the first variable is doing. With a little persistent cut-and-pasting we find:

$_8b7b1f56= create_function(" ",base64_decode(" [a whole jumble of text] "));

Ah, now something we've seen before! Saving the base64 jumble and echo'ing it to the browser gives us:

Which is a serious wall of ascii jumble! Letting the browser do the work for us again, we can cut and paste the code to a different script, change the final "eval" statement to "print", and again check the browser for the ascii-decoded results. The result includes html code that your browser will interpret, so to get to the raw script we need to look at the page source instead:

This little portal, in turns out, was the vector by which all my php files got corrupted.  Apparently some ne'er-do-well from Montreal with ip address POST'ed his nefarious worm to this script and seconds later my entire account had a big ol' Canadian turd dropped on its face.  

And . . . strangely enough, the attack happened at 12:21 pm, or PST server time of 2012-02-20 12:21.  An ominous portent of a long foretold apocalypse?  A happy instance of numerological pranking?  Or the desperate reaching of a paranoiac trying to make sense of his recent victimhood?  Probably not quite the end of days, which would've been 2012-12-21.  But maybe, just maybe, the beginning of the end.

And all that work just to do this?!?!*:

And this?!?!*:

*(The SEO-motivated medicine peddling was probably done via webshell and not through the POST injection script that resulted in an account-wide corruption of my php files.  So, yes, my blog title may have mizled you.)

And How Do We Deal with It?

Now that we've ID'ed the open directories and removed those injection points, we're going to have to search the other directories for similar web shell scripts and POST portals.  I should emphasize that no matter what other precautions you take (changing passwords, changing directory permissions, reverting to older backups or database instances, etc.) so long as these files can be accessed from a browser your entire account is compromised.  You absolutely must delete these files or at least move them off your domain tree so that they are not read by the server.

These shell commands may take a while so maybe fire up a few shells and we'll write these to files for later reference.  Run these commands from either your home directory or your domain root; they will look recursively deeper into the current directory.

Look for the unencrypted web shells:

find . -type f -name "*.php" | xargs grep -Hl "WSO_VERSION" > removethesefiles.txt

And for those hidden in a preg_replace expression:

find . -type f -name "*.php" | xargs grep -Hl '\\x65\\x76\\x61\\x6C\\x28' > removethesefiles2.txt

And for the POST portal:

find . -type f -name "*.php" | xargs grep -Hl "\$_8b7b"  > removethesefiles3.txt

Then scrub all of your php scripts for injected eval(base64_decode script:

find ./ -name "*.php" -type f |  xargs sed -i 's###g' 2>&1

find ./ -name "*.php" -type f |  xargs sed -i '/./,$!d' 2>&1

Preventative Measures

So how did they get in?  In this case I think the first breach was most likely another authenticated user on my shared host finding the world-writable directory I'd left exposed (shame on me). In fact, it was at least four different authenticated users, as I can see that they each left behind webshell files not owned by me.  With a webshell implanted,  one login through the browser as the home user (that is, as though they had ssh'ed or sftp'ed onto the system with password verification), and that explains why the POST portal script ($_8b7b="\x63), which you may remember was the originating point of the last attack, showed my userID as file owner.

This shell command will let you see who owns the files of a directory and when they were last modified:

ls -actl

A few other precautions should be taken.  Your database passwords are likely recorded in plain sight in db configuration files, so all of those ought to be altered.  And you might as well change your ssh/sftp passwords while you're at it.

To see if the breach was indeed through your user account, use these two shell commands to create sub-logs of your user activity for this month and the month before:

last -i | grep yourusername8 > thismonth.txt 

last -if /var/log/wtmp.1 | grep yourusername > lastmonth.txt 

where yourusername8 is the first 8 characters of your username. Then use to check the source of any questionable ip addresses.

Final Precautions

From your home directory, look for all files that have been altered since the last attack:

find . -type f ! -name "log" -mtime -4 > hackedfiles.txt

where -4 is the number of days since the attack.

Also, search for files you're not the owner of (the webshells implanted in my world-writeable directories were inserted by a different owner group!):

find . -type f -printf "%u %h %p \n" | grep -v yourusername | grep -v dhapache | grep -v root > notmyfiles.txt 

where yourusername should be replaced with just that. The results can be found in "notmyfiles.txt", located in the same directory you ran the command. If you see "Permission denied" complaints, those are likely folders owned by the root user but you ought to double-check just in case.

Last Thoughts

One amendment to what's being said out there on the internet: these attacks, as far as I can tell, can happen to any php application and on any host server. I've seen pages that implicate GoDaddy and Wordpress regarding these attacks, but I'm neither a GoDaddy customer nor did I have Wordpress on any of my domains.

One disappointment for me after doing all this is still not knowing if it was a person or an automatically executed script that finally pulled the trigger.  I was really hoping to find subsections within these scripts that might point to a self-proliferating hack: portions of code that would climb up and down the directory tree and execute itself, span the server to ID world-writeable directories, record its findings and POST itself to other logs on other servers.  It would've been neat to see the whole life-cycle of a virus, from start-to-end-to-start.  Now I'm left with the sad likelihood that my last week was spent cleaning up after some jerk-off hiding behind an exploited Canadian server.

And if anyone can crack the code as to what is going on with this broken domain list, I'd be really interested to hear what you figured out:

In dealing with the mess I came across a good number of resources: (just kidding!)

Finally, I should say that some of these steps I didn't even need to take because, as a happy groupie of Dreamhost, I had the convenience of letting their domain system restore do most of the work for me!

Addendum, two months later: this has been the most thorough write-up of the issue that I've seen so far, but I think because of a few oddities of my blog (probably the infrequent posting plays a large role) this page doesn't really appear on searches.  Please link to this where you can, perhaps at another forum or site that you earlier found to be a dead-end or not helpful.  Help and be helped, please!

Labels: , , , , , , , , ,