Update 3: I have forked timthumb.php into a new secure thumbnailer project called WordThumb. It’s a complete rewrite of timthumb and is fully backwards compatible. The only code that is recognizable is the image processing code. All file handling has been rewritten from scratch and I’ve fixed quite a few bugs. The project is now live on Google Code and version 1.0 of WordThumb is up for download. You can read more details about the changes in this blog entry about WordThumb.
Update 2: After evaluating timthumb.php I’ve decided the best solution to the security problem is to fork the project and do a line-by-line rewrite. I started work on this a day ago and it will be published on this blog later today. (This was posted on Wednesday at 11am Pacific Time). Please check my blog’s home page this evening (in about 8 hours) and it should be done.
Update: Ben, the developer of timthumb has been in contact and is working on a fix. His own site was hacked Friday using the same method. I’ve submitted a tiny patch and if you’re a solid PHP hacker it’d be great if you could eyeball the code with us and submit a patch (really easy to do on Google code) if you spot any other opportunities for cleanup (there are many). Given enough eyeballs… you know the quote.
The Exec summary: An image resizing utility called timthumb.php is widely used by many WordPress themes. Google shows over 39 million results for the script name. If your WordPress theme is bundled with an unmodified timthumb.php as many commercial and free themes are, then you should immediately either remove it or edit it and set the $allowedSites array to be empty. The utility only does a partial match on hostnames allowing hackers to upload and execute arbitrary PHP code in your timthumb cache directory. I haven’t audited the rest of the code, so this may or may not fix all vulnerabilities. Also recursively grep your WordPress directory and subdirs for the base64_decode function and look out for long encoded strings to check if you’ve been compromised.
How to fix:
Update: As per several requests I’m posting hopefully easy to use instructions on how to fix this. This is for the latest version of timthumb.php version 1.33 available here. Check your version because there are many much older versions floating around.
NOTE: timthumb.php is inherently insecure because it relies on being able to write files into a directory that is accessible by people visiting your website. That’s never a good idea. So if you want to be truly secure, just delete the file using “rm timthumb.php” and make sure it didn’t break anything in the theme you’re using. If you still want to use it but want to be a bit more secure, you can follow the instructions below.
This will disable timthumb.php’s ability to load images from external sites, but most bloggers only use timthumb.php for resizing local images:
- SSH into your web server. You can use “putty” if you use windows and you’ll need to know your username and password.
- cd into your wordpress installation directory. That is going to vary according to which host you’re using or how you’ve installed it.
- You need to find every copy of timthumb.php on your system. Use the following command without double quotes: ” find . -name ‘timthumb.php’ “
- It will show you a list of where timthumb.php is located. You may want to repeat this command using “thumb.php” as some users have reported that’s what it’s called on their systems.
- Edit timthumb.php using a text editor like pico, nano or (if you know what you’re doing) vim. You would type (without double quotes) ” nano directory/that/tim/thumb/is/in/timthumb.php ” for example.
- Go down to line 27 where it starts $allowedSites = array (
- Change it to remove all the sites listed like “blogger.com” and “flickr.com”. Once you’re done the line should look like this from $allowedSites to the semi-colon:
- $allowedSites = array();
- Note the empty parentheses.
- The next line should be blank and the following line will probably say “STOP MODIFYING HERE”
- That’s it. Save the file and you’re done.
Earlier today this blog was hacked. I found out because I loaded a page on my blog and my blog spoke to me. It said “Congratulations, you’re a winner”.
After a brief WTF? I loaded up the dev tools in Chrome and checked what network requests were going out. Ad content was loading and I don’t run ads on my blog. For some reason the content was hidden, perhaps someone gets paid per impression.
I found the hostname the ads were loading from and grepped the WordPress code for the hostname and nothing turned up. Next I dumped the database – in fact all mysql databases on the server and grepped for the ad hostname and still nothing.
Eventually I found it. The hacker had done an eval(base64_decode(‘…long base64 encoded string’)) in one of WordPress PHP files. My bad for allowing that file to be writeable by the web server. Read on, because even if you set your file permissions correctly on the WordPress php files, you may still be vulnerable.
But what I really wanted to know was how the hell he wrote to a file on my machine.
I checked my nginx and apache access and error logs and eventually found a few PHP errors in the apache log that clued me in.
Turns out the theme I’m using, Memoir, which I bought for $30 from ElegantThemes.com uses a library called timthumb.php. timthumb.php uses a cache directory which lives under wp-content and it writes to that directory when it fetches an image and resizes it.
If you can figure out a way to get timthumb to fetch a php file and put it in that directory, you’re in.
The default configuration of timthumb.php which many themes use allow files to be remotely loaded and resized from the following domains:
$allowedSites = array ( 'flickr.com', 'picasa.com', 'blogger.com', 'wordpress.com', 'img.youtube.com', 'upload.wikimedia.org', 'photobucket.com', );
The problem is the way the developer checks which domain he’s fetching from. He uses the PHP strpos function and if the domain string appears anywhere in the hostname, he’ll allow that file to be fetched.
So if you create a file on a web server like so: http://blogger.com.somebadhackersite.com/badscript.php and tell timthumb.php to fetch it, it merrily fetches the file and puts it in the cache directory ready for execution.
[Note: I’m 99% sure this will work on most webserver configurations because the cache directory that timthumb uses is a subdirectory of directories that are allowed to execute files with a .php extension. So unless you explicitly tell your server to not execute .php files in the cache directory, it’ll execute them. ]
Then you just access the file in the cache directory on the target site using your web browser and whatever code came from http://blogger.com.somebadhackersite.com/badscript.php will get executed by the web server.
In my case, this is what the hacker saw when he accessed my site:
It’s called Alucar shell and it’s a php file that contains one massive base64 encoded string that gets decoded and evalled. It’s encoded in an attempt to hide itself.
When you first hit the script it presents you with a login page and once you’re signed in you see the screenshot above. It works quite well actually. Even if the rest of your filesystem is secure, whoever is using it can dump read-only files like /etc/passwd to get a list of user accounts, config files which may contain passwords, etc..etc..
The current version of timthumb has this issue. Since it’s already in the wild and I just got hacked by it, I figure it’s ok to release the vulnerability to the general public.
To check if you have been hacked do the following:
- Sign into your server using ssh
- cd to your wordpress installation directory
- run “grep -r base64_decode *”
- You should see a few occurences but if any of them have a long encoded string between the parentheses, then you’re probably hacked.
- Go into your theme directory and figure out where timthumb.php is.
- You might try “find /your/wordpress/dir/wp-content/themes/YourTheme/ -name “timthumb.php””
- Edit timthumb and remove the list of external websites that content is allowed to be loaded from.
- I have not audited the rest of the code, so this may or may not make it secure.
- The developer really needs to use a regular expression to check the external hostnames images can be loaded from.