A secure rewrite of timthumb.php as WordThumb

Big News [April 24th, 2012]: I’ve launched Wordfence to permanently fix your WordPress site’s security issues. Read this now.

Update 3 (Final): WordThumb has now been merged into TimThumb and has become TimThumb 2.0. Please head over to the TimThumb site now for updates and to get the code.

Update 2: WordThumb can now take screenshots of websites for you and turn them into thumbnails.

Update 1: Two minor bugs fixed and new minor version released. Thanks guys! You can post bugs directly on this page if you find any more.

I’ve done a full top to bottom rewrite of timthumb and forked the project as WordThumb. You can find it on Google Code with basic instructions on how to use it. Please report any bugs to me at mmaunder at gmail as soon as you can. The code is tested on Ubuntu Linux under Apache and works great.

The only code that is still original timthumb code is the image processing routines. Everything else has been rewritten from scratch. Here are the changes:

  • Code is now object oriented PHP and is much more manageable and readable. It will still run just about anywhere.
  • Fully backwards compatible with all timthumb’s options.
  • Uses a non-web accessible directory as cache for security. By default it uses the system temporary directory. There is a config option to override this.
  • All cached files have a .txt extension as an extra precaution.
  • Cache cleaning has been rewritten to be faster and only run once a day (user configurable) with no contention between processes.
  • ALLOW_EXTERNAL now works as expected. If disabled, you can’t load external files.
  • mime type checking is improved. Previously files would be written to a web accessible cache before the mime check step. Now the furthest a non-image will get is a temporary file which fails a mime check and is deleted.
  • Previously, the check_cache function created a directory with 777 permissions. That’s removed and we simply use the system temporary directory for everything cache related now.
  • Writing images uses file locking now to avoid two processes writing to the same image file and corrupting it.
  • We now use temporary files when fetching remote images rather than using the same filename we’re turning into a thumbnail. This avoids another process on a busy server thinking a file is a cached thumbnail and serving an unprocessed image accidentally.
  • Fixed browser headers like accept-ranges.
  • Improved error reporting.
  • Added debug mode with tons of debug messages.
  • Debug messages include benchmarking to see where slowdowns occur if any. (It’s very fast!)
  • Cleaned up conflicting curl options like CURLOPT_FILE
  • Added ability to disable browser caching for debugging
  • Added clarity on curl timeout (many sites use php’s default fetching which doesn’t have a timeout)

46 thoughts on “A secure rewrite of timthumb.php as WordThumb

  1. Since their story wouldn’t sound right within the context of my reality, I translated
    the vibration in a story that may have put on me. Therefore, if you have accidently engaged a Trojan virus program, there’s
    no escape until it fully enters your computer. Easy when you’re
    conscious what you are looking for in a product and difficult
    once you don’t have something for choosing the
    best Clickbank products.

  2. Today, I tried to add a new post to my blog after about 6 weeks from past post. And now, timbthumb no longer seems to be generating the thumbnail. What is generated in the html where the thumb should be is:

    where previous thumbs had the correctly generated url:

    I reuploaded the latest timthumb.php, have the file with 755, and the cache and thumb directories with 755 as well. I have changed nothing to the WordPress theme files for gridnik since my last successful post in August. I am at a loss of what is wrong or how to fix it, but the entire theme depends on timthumb in its template_tags file, and now I cannot have a thumb show up for my new posts. PLEASE HELP!!! (NB: I am not a programmer) TIA

  3. Hi,

    Thanks for the update. Still having issues though.

    On our site we have two ways of fetching an image – 1. Getting the src from the ‘Featured Image’ via wp_get_attachment_image_src and parsing this to timthumb – 2. By getting the value of a custom field containing an image URL.

    Now, the second method works as it does not contain: http://***.com/ in the custom field value. The first approach which contains the full image URL does not work at all.

    This was not an issue with the old script and has only started since upgrading timthumb.

    Please advise.

    Many thanks.

  4. (Repost of a Q posted elsewhere on this blog; apologies, I just hope to catch someone’s eye before all is forgotten!:)

    Question: We, too, were victimized by this exploit. The odd thing is that it only showed up in one way: Macintoshes were not redirected, nor were PC’s *except* for those clicking on the link provided on the site owner’s Facebook page. The lins on that page, posted automatically when each post is published, are routed through facebook with some sort of hash number I suppose is designed to authenticate the link redirect. I thought the problem might be the URL shortener, but the problem occurred either way. E.g., http://ow.ly/62vj6 (the site has been repaired, of course)

    Any idea why just one route resulted in spamsville? I’m suspecting a defect in Internet Explorer, the probable brower used.

  5. I just had this happen to my personal WP self-hosted blog, (via I am guessing, a Flickr widget for the Gridnik theme that uses timthumb to generate thumbs) and while I was able to restore back to before the attack, August 6, I still don’t grasp how to keep using timthumb without getting hacked again. I am a designer, not a programmer or database person. I use Transmit to upload and modify files. Can someone provide instructions “for dummies” as to how to increase security to keep using timthumb? OR: how to use built-in WP API and flickr API to pull flickr images and size them to 60×60?

    • Chris, timthumb.php is now a lot more secure. We do mime checking which prevents script uploads, we use a .txt extension to prevent server execution of cached files, and the cached files have a secure header that also prevents execution. Grab the latest and you’re good to go.

      Regarding wordpress core: Read this: http://codex.wordpress.org/Embeds

      Then you can try something like this:

      [embed width=375]
      http://www.flickr.com/photos/otto42/5079283523/
      [/embed]

      • Hi Mark: I did replace my timthumb with the new version, but I still think something is going on: the cache folder is having index.php and index.html files inserted into it, and on August 6, several php files “appeared” in the WP directories, such as a config.php where it didn’t belong, with Russian characters in it, alterations to index.php to add an include…in other words lots of funny stuff. So I am going to try to figure out how to modify Gridnik’s flickr widget php to not use tim thumb to generate 60×60 thumbs, because I’m worried about bad stuff happening that I don’t understand, being mostly a designer and not a programmer. What I find puzzling in the 2.4 of timthumb is that it still has .cache directory as default, rather than turned off for security’s sake. I turned it off and now a directory titled FILE_CACHE_DIRECTORY has appeared. Is that what is supposed to happen?

        • timthumb creates the index.php and index.html files as a security measure to prevent directory listing of the cache directory.

          Don’t know about the rest, although the FILE_CACHE_DIRECTORY suggests you may have edited the php code incorrectly.

  6. This looks much better than original timthumb.

    Regarding Tom’s comments about image source URL in query string – I think also that it’s a potential security problem.

    I have an idea how to remove that threat – to make a two-way encryption of the whole querystring submitted to wordthumb.

    The only downside will be that it wouldn’t be a ‘drop-in’ replacement anymore.

  7. Hey mark,

    First of all congratulations on doing this!

    While trying this out on an ecommerce platform I have ran across some issues. Somethimes wordthumb just creates cached image files of 0 bytes.

    Is it possible to add an extra “verification” that verifies the filesize whether it’s 0 bytes or not, if so, it removes the file.

    Ill try and have a look at it myself also and will post here if I come up with something.

    Thanks,
    Kenny

  8. I wish two things: that you put this effort into working with Ben on TimThumb and improving that (much more widely used) code, and that you weren’t encouraging people to get a plugin from someplace not the official directory, which we discourage quite a bit to avoid people not getting updates or accidentally downloading something with malware.

    • Matt I’m going to assume this is actually you (I checked and the IP is SF).

      Timthumb isn’t in the wordpress directory – I’m assuming because it’s not a wordpress plugin. Unless I missed something or misunderstood.

      The code is not in good shape, so I did a ground up rewrite, which I think needed a fork. It’s 90% new code and adds features like website screenshots.

      As a few folks have commented on the web, timthumb’s name is so tarnished that an alternative with a new name that is more secure is probably not a bad idea.

      We also seem to disagree on the architecture. My view is that timthumb should be using a private cache directory and Ben seems to differ. The public cache is what caused the hack.

      Mark.

      • In core WordPress we disagree all the time — it’s not a reason for a fork. It’s an opportunity to discuss the issues more until you come to a consensus. I don’t think the name is tarnished any more than WP’s own has been in the past. These things happen and it’s not the end of the world, it’s a chance for us to come closer together not further apart.

        • Fair enough. I’ll contact Ben and see about merging my code into Timthumb. I’ll cc you on the email in case you want to weigh in. Thanks for the feedback.

  9. Will this work in a network WordPress installation? I know there was an issue with TimThumb not working well in a network’s themes due to image path issues.

    Thx.

    • Yes it will. In fact it doesn’t even need WordPress. You can install it on any server that will run PHP and it will just _work_.

      It will work with any other publication platform like Drupal and Joomla. As long as your web server runs PHP code, it will work.

      • Mark,

        I believe he is referring to the fact that with a multi site wordpress installation, the image locations need to be re-routed through the multi site media path. I’ve written a function to take care of that, if you would like I can send you a copy of the function, for reference. It uses the upload directory for the current blog to automatically convert the url to a relative path that works with timthumb/wordthumb. With this function you can use timthumb/wordthumb with both multi site and non-multi site wordpress installations.

  10. Mark,
    This sounds like a nice improvement. I don’t have the time right now to get into the code myself and try adding this or I would, but I can pass it on for now.

    This is a url re-writing process that makes all image urls look like this:
    http://latte.photographyblogsites.com/seo-gallery/thumbnails/960-200-90/latte.photographyblogsites.com/files/2011/07/gallerythumb4.jpg

    That is a working image. You can adjust the “960-200-90″ and see it dynamically adjust width, height, and quality in that order.

    The “latte.photographyblogsites.com/files/2011/07/gallerythumb4.jpg” is the full path to the image without the “http://”.

    I just wanted pretier URLS and to have them look like image URLS for SEO purposes, after reading that google interprets timthumb images as .php files (which it is really). But after seeing some of the comments about this security flaw, I imagine it would help there as well because it hides the fact that this is a file that is being sent GET info. The location of the file and the GET info are all completely hidden.

    This is how it was done…

    The following would go in a plugin or theme’s function.php:


    add_filter( 'rewrite_rules_array' , 'timthumb_insert_rewrite_rules' );
    add_filter( 'query_vars' , 'timthumb_insert_query_vars' );
    add_action( 'wp_loaded' , 'timthumb_flush_rules' );
    add_action( 'template_redirect' , 'timthumb_redirect_template');

    // flush_rules() if our rules are not yet included
    function timthumb_flush_rules() {
    $rules = get_option( 'rewrite_rules' );

    $myrules = array (
    '('.$slug.')/(thumbnails)/(.+)/(.+)$',
    );

    $need_to_flush = false;

    foreach ( $myrules as $rule ) {
    if ( !isset( $rules[$rule] ) ) {
    $need_to_flush = true;
    }
    }

    if ( !$need_to_flush ) {
    global $wp_rewrite;
    $wp_rewrite->flush_rules();
    }

    }

    // Adding a new rule
    function timthumb_insert_rewrite_rules( $rules ) {

    $newrules = array();

    // timbthumb...
    // path/to/slug/thumbnails/w-h-q/imagepath
    $newrules['('.$slug.')/(thumbnails)/(.+)/(.+)$'] = 'index.php?thumb-info=$matches[3]&thumb-url=$matches[4]';

    return $newrules + $rules;
    }

    // Adding the id var so that WP recognizes it
    function timthumb_insert_query_vars( $vars ) {

    array_push($vars, 'thumb-url');
    array_push($vars, 'thumb-info');
    return $vars;
    }

    function timthumb_redirect_template() {

    global $wp_query;
    $query_vars = $wp_query->query_vars;

    if( isset( $query_vars['thumb-info'] ) ) {
    include PATH_TO_TIMTHUMB_SCRIPT;
    exit;
    }
    }

    Then the thumb script needs to recognize the info sent in $wp_query with the addition of something like this to the top of the timthumb (or wordthumb) script:


    global $wp_query, $wpdb;

    $query_vars = $wp_query->query_vars;

    $info_all = explode( '/' , $query_vars['thumb-info'] );
    $info = array_shift( $info_all );
    $info = explode( '-' , $info );

    $q = ( explode( '/' , $info[3] ) );

    if ( isset( $info[0] ) ) $_GET['w'] = $info[0];
    if ( isset( $info[1] ) ) $_GET['h'] = $info[1];
    if ( isset( $info[2]) ) $_GET['q'] = $info[2];

    $image = $query_vars['thumb-name'];

    $url = implode( '/' , $info_all ) .'/'. $image;
    $url = str_replace( 'http://' , '' , seo_gallery_url_for_multisite( $url ) );

    $src = $url;

    $_GET['p'] = 'http';

    There are some obvious ares for improvement:

    1) Right now it uses WP rewrite rules and the template redirect but I feel it would be better if it used WP’s external rewrite rules as it would be much lighter than loading WP and the template redirect for each image. But it is working pretty weel for now…

    2) it assumes all urls are http, not https or anything else. in order to keep it looking like an image URL, I took out the “http://” when sending the URL.

    3) I only have it taking width, height and quality, but that ‘thumb-info’ variable is just exploded by each ‘-‘ so you could add as many other parameters as you want.

    Hope it helps!

  11. i was looking forward to an easy swapping out of timthumb in favor of wordthumb, but i dropped wordthumb into timthumb’s directory and did a search and replace across files to change timthumb to wordthumb. thought that’d make all systems go, but instead :

    http://donutsites.com/sandbox01/wp-content/themes/Arsonal/includes/wordthumb.php?src=http://donutsites.com/sandbox01/wp-content/uploads/2011/06/BATTLE_LA_Wall_5.jpg&h=690

    produces the following error:
    You may not fetch images from that site.

    i’m not even fetching from an external site, so i’m not sure what’s up.

  12. Looks good! Just one small bug – it doesn’t let you use any spaces/%20 characters in the URL/file path. Any ideas how to fix?

    Thanks!

  13. I gonna take a look at it; if it works out good, I might even fork it myself to integrate my ImageMagick-focused rewrite of TimThumb with it ;)

    cu, w0lf.

  14. A vast improvement, thankyou!

    My only point is that this still relies upon the problem that the source file is still specified in the URL which is inescapable in this design, and is the major source of insecurity here.

    Currently the only immediately obvious hole apparent is that you could use this to test the existence of non-image type files. But that this script displays a verbose error message is already a big improvement over timthumb.php

    Since this is clearly intended for WordPress, I would advise saving yourself code duplication where possible and using the WordPress APIs for file storage, and including the files as an attachment where possible:

    http://codex.wordpress.org/Function_Reference/wp_insert_attachment

    This code will take your temporary file and put it into the WordPress uploads folder:

    $uploads = wp_upload_dir();
    $uniquefilename = wp_unique_filename($dir,$filename);
    $new_file = $uploads['path'] . "/$filename";
    if ( false === @ move_uploaded_file( $file['tmp_name'], $new_file ) )
    return sprintf( __('The uploaded file could not be moved to %s.' ), $uploads['path'] );

    $url = $uploads['url'] . "/$filename";

    You can then take the $new_file variable and use it to create an attachment that can be stored and used in the media library:

    $attachment = array(
    "post_title" => $show . " - " . $title . "(Thumb)",
    "post_content" => "",
    "post_status" => "draft",
    "post_mime_type" => "image/jpeg"
    );

    $id = wp_insert_attachment($attachment, $new_file, $post_id );

    $metadata = wp_generate_attachment_metadata( $id, $new_file );

    $result = wp_update_attachment_metadata( $id, $metadata );

    Which you can then display at any size you want:

    • Tom I disagree. Wordthumb does a lot more than the wp api can. Also the source of insecurity was writing external files to a web accessible dir which is fixed. This is also designed to work with any app, not just WordPress.

      Your idea of putting fetched files in the uploads dir adds back the vulnerability.

      • No because you would check it prior using wordpress APIs. Reference wp_handle_upload for how to deal with error checking and mimechecking and capabilities.

        And what’s to stop you working on a temporary file beforehand applying the image filters etc?

        Eitherway the whole concept of putting it in via the URL GET is the crux of the problem. If it’s not the execution of php script its the inclusion of javascript or specifying a decompression bomb in the src field etc

        This script is not ‘secure’ and can never be, no script can, your engineering your way around a design flaw. It would be safer to work with the WordPress APIs internally where possible, they’re better tested, more likely to be up to date, and have a lot more developers working on them, and as such as more reliable.

        There’s no reason why a version of wordthumb couldnt be built that was included in functions.php and hid the entire process from the user, including its presence using the wordpress APIs. This would dramatically improve the security of the file, by removing the problem of GET variables entirely as well as any public facing interface, requiring the attacker to already have php access in order to launch an attack, defeating the entire point.

        It neednt even use wordpress APIs, and instead add a function that took the same parameters and returned a URL, and it would still be a vast improvement yet again over this script as it currently stands.

      • Just to clarify the last part, if the resized images where stored in a publicly accessibly folder, that was read only, and the URLs where handed as is, yet the generation and specification of which file needs resizing was handled in the sites PHP code during page generation, then this is what would improve security.

        The reason it would improve security is because end users can no longer modify the url string and cache/resize whatever they want. They can no longer abuse your resources to get custom sizes of the images on your site, or specify URLs to php shells, decompression bombs or other malicious files.

        The only files that would be accessible are the ones you put there via your site. In the case of WordPress, one would need to hack WordPress itself or whichever application you built, be it with codeigniter or drupal, etc.

        This would also hide the presence of the script from the end user browsing the site, further increasing security. This complete removal of the public facing API would be the main improvement.

        • would love to see a forked vs. of what you are talking about here. I agree and prefer using the api wherever possible.

          I’ve followed your comments but am having a hard time putting it all together in a working vs.

  15. Noob alert

    Since I am unsure where all timthumb.php is being called, can I simply save WordThumb.php to timthumb.php? Or is there any simpler and faster way to replace the calls?

  16. Well, it’s nice to read that you’ve rebuilt it from scratch! :D
    Thank you for your hard work, I love to see that you didn’t rework that blob :D

    Just looking at the code: could you prefix it to make it compatible with autoloaders? (i.e. WordThumb_Thumb)
    Also, could configuration be passed in at construction time instead of using constants? :)

    Thank you anyway, sorry if I can’t fork it and rework it, but I’m currently rather busy… Hope you don’t throw away the suggestion :D

    • Thanks Marco, will take a look at the autoloader issue.

      The constants for config are syntactical sugar, but I’ll give it some thought and see what I can do.

      Thanks for the suggestions!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.