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.
Source: Tips4all
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
ReplyDelete+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.
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.
ReplyDeleteThe 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.
Ok, how about another way (this is a tad hacky, too, by the way)
ReplyDeleteWhen you go to the gallery page, you will know which images will need to be sent to the user. I'll assume that they have an identifier (filename/id/whatever). Generate a list of all the files required for this request and store that somewhere easily accessible by your scripts, for example, in a text file on the server (not publicly accessible). Give that file an id.
Now, have another PHP file which doesn't load up the full Zend framework. It should get some parameters from the URL: the image file being requested, and the id of the list.
Write the URLs into the img tags like so:
<img src="image.php?file=myFile.jpg&list=12345" />
The image.php file should open the list with that id and check if myFile.jpg exists in it. If so, show it, if not issue a 404.
And don't forget to clean up your old lists regularly.
I was thinking of such aproblem some weeks ago, and ended up writing an own Thumbnail ViewHelper, just like you did. To original images are stored in a non-public directory. So each time i do a call like
ReplyDeleteecho $this->thumb(array('url' => HIDDEN_DIR . 'foo/bar.jpg'));
the ViewHelper will copie the Image a public accessable cache dir and return the url to the caced image:
/_cache/thumbs/3858f62230ac3c915f300c664312c63f.jpg
Additional, the script can crop and resize images, if neccessary. Also, I added a feature to clean up 1000 Images from the cache every 50 calls...
The only thing to remeber is that you never show the path to your directory with the original images, even if it is not accessable from public. Then, I would say it is secure.
The script takes, with cache enabled, no noticable time, so it provides a good user experience. I hope you can use some of my ideas ;)
how about putting all the images in a public directory, configured not to have directory listing, and use very long and random filenames (generated with md5 for example, from an id and some salt) that is tracked in a database, and the filename changed after each access.
ReplyDeleteDatabase holds... filename:'myfile.jpg',tempname:'uysdfnasdufhansvdufgnvasoeuvncas.jpg'
Access function looks up tempname in database and inserts url accordingly
After access, filename stays the same, tempname changes and file is renamed to new tempname
What are your security considerations? What is your threat model? How do these solutions address this?
ReplyDeleteNeither solution addresses leeching. Neither solution addresses redistribution of malware. Neither solution (as presented) restricts access to content items.
Your teammate's solution is flawed in that it loads the entire image into PHP's memory unnecessarily.
Your solution is not very efficient since it requires 2 reads and a write operation in place of a single read, and you're only reducing the window of opportunity for unrestricted access to the content.