Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it secure to place uploaded images in a public folder?

I've just had a discussion with my teammate about the location of user uploaded images in an image gallery. I would like a broader insight on the methods we suggest.

My teammate wrote a controller + action that calls file_get_contents on an image file placed in a folder that's not available for public browsing (i.e., outside public_html on the server), and echoes it via a header. This is secure, but since we use Zend Framework, it's also crawling slow - each call to the image controller costs us approx 500ms of lag due to the bootstrap's queries being executed. It's annoying since the picture gallery view displays over 20 images at the same time.

In short, the relevant code would be:

class ImageController extends Zend_Controller_Action {
    public function showAction () {
        $filename = addslashes($this->_getParam('filename'));
        if(!is_file($filename)) {
            $filename = APPLICATION_PATH.'/../public/img/nopicture.jpg';
        }
        $this->_helper->viewRenderer->setNoRender(true);
        $this->view->layout()->disableLayout();
        $img = file_get_contents($filename);
        header('Content-Type: image/jpeg');
        $modified = new Zend_Date(filemtime($filename));
        $this->getResponse()
             ->setHeader('Last-Modified',$modified->toString(Zend_Date::RFC_1123))
             ->setHeader('Content-Type', 'image/jpeg')
             ->setHeader('Expires', '', true)
             ->setHeader('Cache-Control', 'public', true)
             ->setHeader('Cache-Control', 'max-age=3800')
             ->setHeader('Pragma', '', true);
        echo $img;
    }
}

Then, in a view, we just call:

<img src="<?php echo $this->url(array('controller' => 'image', 'action' => 'show', 'filename' => PATH_TO_HIDDEN_LOCATION.'/filename.jpg')); ?>" />

I have a different approach: I prefer to keep the original images in a hidden location, but as soon as they are requested, copy them to a public location and provide a link to it (with an extra mechanism, run by cron, to wipe the public images directory every now and then in order not to waste space, and a robots.txt telling Google not to index the directory). The solution places files (a few at every given moment) in a publicly accessible directory (provided one knows the filename), but also requires only a view helper, thus not launching the bootstrap:

class Zend_View_Helper_ShowImage extends Zend_View_Helper_Abstract {
    public function showImage ($filename) {
        if (!file_exists(PUBLIC_PATH."/img/{$filename}")) {
            if (!copy(PATH_TO_HIDDEN_FILES."/{$filename}",PUBLIC_PATH."/img/{$filename}"))
                $url = PUBLIC_PATH.'/img/nopicture.jpg';
            else
                $url = PUBLIC_PATH."/img/{$filename}";
        } else {
            $url = PUBLIC_PATH."/img/{$filename}"
        }
        return "{$url}";
    }
}

With the aid of this helper, the call is very simple in the view:

<img src="<?php echo $this->showImage('filename.jpg'); ?>" />

Question: Does my approach pose a security threat, as my coleague states? What are the potential risks of this? And, most importantly, do the security threats, if any, outweigh the 10 seconds gain on page load?

In case it matters: we're working on a community portal with around 15K registered users, with the galleries being a very frequently used feature.

*The code I pasted is an edited, simplified version of what each of us has come up with - just to show the mechanics of both approaches.

like image 775
mingos Avatar asked Jul 08 '11 09:07

mingos


People also ask

What's the best way to store user uploaded images?

Store the images as a file in the file system and create a record in a table with the exact path to that image. Or, store the image itself in a table using an "image" or "binary data" data type of the database server.

Is Dropbox good for sharing photos?

Dropbox makes it easy to upload, organize, and share your photos using cloud storage. The file-sharing process is simple and safe whether they're from a vacation or your professional portfolio. You can share a link to your photos or send an email invitation via Dropbox.


2 Answers

I have a different approach: I prefer to keep the original images in a hidden location, but as soon as they are requested, copy them to a public location and provide a link to it

+1 for creativity.

Does my approach pose a security threat, as my coleague states? What are the potential risks of this? And, most importantly, do the security threats, if any, outweigh the 10 seconds gain on page load?

Sort of. Yes, if you have images only some people are allowed to see, and you're putting them into a publicly accessible directory, there is a change other people can see that image, which appears to be undesirable. I also don't think (might be wrong) that it will gain 10 seconds on a page load, as you'll have to copy the images, which is a rather intensive operation, more than using file_get_contents or readfile( ).

This is secure, but since we use Zend Framework, it's also crawling slow - each call to the image controller costs us approx 500ms of lag due to the bootstrap's queries being executed.

If I may suggest; nuke Zend Framework for this specific case. I'm using Zend Framework for a rather large website as well, so I know the bootstrap can take longer than you want. If you circumvent Zend Framework, opting for vanilla PHP, this would improve the performance significantly.

Also, use readfile( ), not file_get_contents( ). There's a big difference in that file_get_contents will load the whole file in memory before outputting, where readfile does this more efficiently.

like image 171
Berry Langerak Avatar answered Oct 09 '22 21:10

Berry Langerak


As long as you make sure that the file really is an image file by checking MIME, magic bytes and prehaps even trying to load the image using the GD library you should be fine.

The initial approach could probably be sped up by using a specialized bootstrap that don't execute your entire bootstrap. Just the essentials for you to load your image.

You could also place a "normal" .php file in your public folder that contains a script that opens and outputs the images from your non public folder using GD or similar. Just call it with something like

<a href="image.php?image=foobar.jpg&width=320&height=240" />

Here you would also have to make sure you don't blindly trust the image name but actually checks that this is an image in your image directory and not something nasty.

I could also recommend having your images stored in a non public folder that the web server has write access to and then creating a symlink without write access and directory listing in the public folder to this image folder and load the images from there. Your web server could serve them directly and not have to copy them.

Just a few suggestions.

like image 4
inquam Avatar answered Oct 09 '22 20:10

inquam