I am building a small web app that will allow a logged in user to upload product pictures. I'm trying to build-it-right, and after some naive implementations in the past, decided to add all uploaded content in my public folder.
\app (public folder)
-\index.php
\includes
\config
\uploads
This would make it so if a malicious user managed to upload a PHP file it wouldn't be reachable or served by server.
That being said, how would I go about serving these pictures on the webpage? What is the best way to do this? In the past I've used a getImage.php file that would take an ID parameter (and a hash of some generated value to avoid users just guessing IDs) which would look like "getImage.php?id=555&c=44j54k3h5". The server grabs the image information in the DB (the real path) and loads the content, rebuilds the header, and sends the image.
Are there any security concerns regarding this method? Is there a way to make it prettier? Would having a htaccess rewrite rule that looks for /images/imagename.jpg and sends them to my getImage.php file add security holes? Any advice on best practices would be greatly appreciated.
Check out this class. It will handle the upload, do security checks and make you coffee :)
http://www.verot.net/php_class_upload.htm
The security of your method of serving the uploaded image, really do depend on a lot of other factors.
How is the image/file treated and secured on upload? What are the
directory/file permissions for the upload?
What processes have accessto read from and write to those locations?
What type of file handlers do you have that may execute arbitrary files?
If you're going the PHP->get_image->display_image route, does it attempt to prevent malicious intent AFTER upload (on the getimage?)
I would use Apache mod_alias to reference the directory above webroot. This way, you would be able to still utilize links to images without opening the full above-root directory to exposure.
The "prettier" method would be to use a .htaccess redirect
RewriteEngine On
RewriteBase /
RewriteRule ^getImage/([0-9]+)/(.+)\.png$ getImage.php?id=$1&c=$2 [NC,L]
Then in getImage.php
if (isset($_GET['id']) && isset($_GET['c'])) {
// set headers
header("Content-Type: image/png");
// cache control
header("Cache-Control: private, max-age=10800, pre-check=10800");
header("Pragma: private");
header("Expires: " . date(DATE_RFC822, strtotime("2 day")));
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
header('Last-Modified: ' . $_SERVER['HTTP_IF_MODIFIED_SINCE'], true, 304);
exit;
}
/* do things */
imagepng($image_resource);
imagedestroy($image_resource);
}
else {
echo "bad request";
}
So instead of
site.com/getImage.php?id=555&c=44j54k3h5
You would use
site.com/getImage/555/44j54k3h5.png
There aren't any added security risks or loop hole with this method. Just make sure your get variables are escaped before querying to avoid injection attacks.
If you want to protect yourself from malicious file uploads, you need to do that before serving.
Related
My site uses bookmarklets to gather data from external sites, kinda like Pinterest. I'm concerned about security and want to move the images the bookmarklet gathers from the doc root up one level. My script has some hefty security checks in place, but I want to add this as a last line of defense.
How do I access my images within my script? Obviously using ../userimages/id/image.jpg wont work. I'm using Apache.
Thanks!
Proxy the image
You would use a proxy script to feed the images through like the following example:
// open the file in a binary mode
$name = '../userimages/id/image.jpg';
$fp = fopen($name, 'rb');
// send the right headers
header("Content-Type: image/png");
header("Content-Length: " . filesize($name));
// you may like to set some cache headers here
// dump the picture and stop the script
fpassthru($fp);
exit;
This example is from the PHP manuals fpassthru() page. You would save this script somewhere in your servers document root/httpdocs folder.
"Spoofing" the URL to the image
The easiest way to give the PHP file the appearance of being an image file to a user/browser is to use Apaches mod_rewrite. Usually I use a URL structure something like this:
http://www.example.org/image-id/image.png
Where image-id is the unique identifier for that particular image. This way the file has the correct extensions of an image instead of .php.
I'm using a PHP proxy script to load images from Facebook into Flash without any sandbox violations. It is taken from the guide here: http://www.permadi.com/blog/2010/12/loading-facebook-profile-picture-into-flash-swf-using-open-graph-api/. The relevant PHP code is:
<?php
$path=$_GET['path'];
if (stristr($path, "fbcdn.")==FALSE && stristr($path, "facebook.")==FALSE)
{
echo "ERROR";
exit;
}
header("Content-Description: Facebook Proxied File");
header("Content-Type: image");
header("Content-Disposition: attachment; filename=".$path);
#readfile($path);
?>
The guide mentions that additional security measures are recommended for a real world application. What additional measures would be applicable to this? Maybe some kind of key passed from Flash to PHP?
I realise that there's nothing I can do to completely protect the Flash from being decompiled, but can I prevent the script from being used maliciously?
You should restrict the proxy to fetching image files from Facebook. You current "protection" will allow for example this URL: http://virus.provider.com/fbcdn./virus.exe
Make better checks of the domain bname, maybe using the parse_url function.
Check that you are indeed serving only images. Make sure the filename is ending in a image extension (this helps a lot for Windows clients), but also consider doing more thorough checks of the actual file content.
Consider adding a check of the $_SERVER['HTTP_REFERER'] to lower the incentives to use your script for hotlinking. If the HTTP_REFERER is non-empty, check that it's actually your site in there. This will mostly protect you from bandwidth thieves.
Make sure it's actually a remote path. Your current script can be tricked to sending for example your PHP files unparsed, including passwords and other secrets!
The filname in the Content-Disposition header should be set to a filename, not to the entire path.
Also consider caching the file data on your proxy server to speed up multiple calls to the same file.
These are a few of the things to keep in mind. You may reveal more if you put some thought into it.
For an image file (JPEG) that has been uploaded to the server via a PHP script (to a directory such as http://www.somedomain.com/images, is it a good idea to allow the client to get the image's direct address (such as http://www.somedomain.com/images/someimage.jpg and paste it into a WYWSIWYG text editor (such as TinyMCE)?
I am wondering if there is a preferable method where the direct address is encrypted?
Please, if I should just link directly to the image, just say so.
Thanks!
Note: I have modified this question from my original. Please see revisions if you are curious, but I think I was asking the question incorrectly. My apologies to the people who already answered.
As long as you check correctly WHAT is being uploaded, it shouldn't be a problem. So please at least use getimagesize or a similar function to make sure it's an image that's being uploaded, AND make sure the extension on the file is correct so that it will never be run through the PHP interpreter - to prevent someone from uploading an image with a PHP script attached.
BTW Here's a nice whitepaper on uploads and security : http://www.scanit.be/uploads/php-file-upload.pdf
Depending on the CPU Constraints of your web-hosting service you can write a service to 'serve' the images to your users.
Here is some very BASIC code, it needs spiffing up and cleaning up for XSS/etc...
<?php
$basePath = "/path/to/my/image/store/not/web/accessible/";
$file = NULL;
if (isset($_GET['file']))
$file = $_GET['file'];
if ($file != NULL)
{
$path = $basePath . $file;
// $file needs to be checked for people
// trying to hack you, but for the sake of simplicity
// i've left it out
$mime = mime_content_type($path);
$size = filesize($path);
header("Content-Length: " . $size);
header("Content-Type: " . $mime);
header('Expires: 0');
readfile($path); // Outputs the file to the output buffer
}
?>
Obviously you can put whatever security checks in here you want. But this way your files are below the web dir, and you can apply logic to thier accesibility. This is typically used more for FILE vs. Images, but you can do the same thing here.
Images Accessed like this
http://www.mysite.com/image.php?file=hello.jpg
And you can use mod_rewrite to rewrite urls like this:
`http://www.mysite.com/images/hello.jpg
Into the first url.
I Cannot stress enough the need for further security checking in the above example, it was intended to show you how to serve a file to the user using PHP. Please don't copy & use this verbatim.
Wordpress uses direct links for images. The permalink function simply puts the image on a page along with metadata for comments, but the images' SRC attributes still link directly to the image.
why are you concerned about revealing your image location. Hotlinking?
if so you can prevent hotlinking with htaccess
http://altlab.com/htaccess_tutorial.html
Didn't you get your answer already?
Every site reveals image location to the browser. It's just the way web works.
Got any reason to "encrypt" original location?
i made an image resizer in php. When an image is resized, it caches a new jpg file with the new dimensions. Next time you call the exact img.php?file=hello.jpg&size=400 it checks if the new jpg has already been created.
If it has NOT been created yet, it creates the file and then prints the output (cool).
If it ALREADY exists, no new file needs to be generated and instead, it just calls the already cached file.
My question is regarding the second scenario. Which of these is faster?
redirecting: header('Location: cache/hello_400.jpg');die();
grabbing data and printing the cached file: $data = file_get_contents('cache/hello_400.jpg'); header('Content-type: '.$mime);
header('Content-Length: '.strlen($data));
echo $data;
Any other ways to improve this?
If someone wants the generated code, check this out:
http://egobits.com/misc/img.phps
Thanks to all for the help!
I would opt for never printing the data to the browser. Both scenarios should throw a permanent redirect to the generated image. Except if the image doesn't exist yet, it is created before the Location header is sent.
Edit:
Just to be clear about what I mean by permanent redirect...
header('HTTP/1.1 301 Moved Permanently');
header('Location: http://path/to/image');
Maybe you could do the following:
Set some directory for these images.
Link to images in this directory (<a href="/img/resizable/hello_400.jpg>).
Set your webserver to redirect to your php script if the image doesn't exist yet. If you are on Apache, a simple .htaccess will do. In PHP, you have $_SERVER["REQUEST_URI"] from which you can tell which image you should resize.
Your script saves and echoes the image (but is called only for the first time).
This way, your get some benefits:
The image is cached (in a proxy or a browser) as any other static file.
PHP doesn't have to be called for every request just to redirect or output statical data.
You leave the implementation of If-modified-since and other cache-related headers to the webserver.
And the links look nicer :-)
Example .htaccess in your /img/resizable folder:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* /img.php [L]
Any other ways to improve this?
Yes.
There is a way to send nothing but just an HTTP header: a conditional get.
You can take a look at the similar script, http://shiftingpixel.com/2008/03/03/smart-image-resizer/, for the implementation
As a third (more powerful) option: cache the image in a binary field in a database, and query the database for it.
Implement all three solutions and benchmark them.
I'm going to guess that the first option (redirect) will be the slowest in the real world, because it requires just as much effort as the second option (file_get_contents), but involves a second request and more overhead.
If possible in your case, you can also implement a function to directly set the url of the cached image in your html like:
<img src="<?php getImageUrl('hello.jpg', 400); ?>" />
getImageUrl() will return the url of the cached image if it exists else it will return the url to dynamically generate the resized image.
What would be the best practice way to handle the caching of images using PHP.
The filename is currently stored in a MySQL database which is renamed to a GUID on upload, along with the original filename and alt tag.
When the image is put into the HTML pages it is done so using a url such as '/images/get/200x200/{guid}.jpg which is rewritten to a php script. This allows my designers to specify (roughly - the source image maybe smaller) the file size.
The php script then creates a hash of the size (200x200 in the url) and the GUID filename and if the file has been generated before (file with the name of the hash exists in TMP directory) sends the file from the application TMP directory. If the hashed filename does not exist, then it is created, written to disk and served up in the same manner,
Is this efficient as it could be? (It also supports watermarking the images and the watermarking settings are stored in the hash as well, but thats out of scope for this.)
I would do it in a different manner.
Problems:
1. Having PHP serve the files out is less efficient than it could be.
2. PHP has to check the existence of files every time an image is requested
3. Apache is far better at this than PHP will ever be.
There are a few solutions here.
You can use mod_rewrite on Apache. It's possible to use mod_rewrite to test to see if a file exists, and if so, serve that file instead. This bypasses PHP entirely, and makes things far faster. The real way to do this, though, would be to generate a specific URL schema that should always exist, and then redirect to PHP if not.
For example:
RewriteCond %{REQUEST_URI} ^/images/cached/
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule (.*) /images/generate.php?$1 [L]
So if a client requests /images/cached/<something> and that file doesn't exist already, Apache will redirect the request to /images/generate.php?/images/cached/<something>. This script can then generate the image, write it to the cache, and then send it to the client. In the future, the PHP script is never called except for new images.
Use caching. As another poster said, use things like mod_expires, Last-Modified headers, etc. to respond to conditional GET requests. If the client doesn't have to re-request images, page loads will speed dramatically, and load on the server will decrease.
For cases where you do have to send an image from PHP, you can use mod_xsendfile to do it with less overhead. See the excellent blog post from Arnold Daniels on the issue, but note that his example is for downloads. To serve images inline, take out the Content-Disposition header (the third header() call).
Hope this helps - more after my migraine clears up.
There is two typos in Dan Udey's rewrite example (and I can't comment on it), it should rather be :
RewriteCond %{REQUEST_URI} ^/images/cached/
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteRule (.*) /images/generate.php?$1 [L]
Regards.
One note worth adding is to make sure you're code does not generate "unauthorized" sizes of these images.
So the following URL will create a 200x200 version of image 1234 if one doesn't already exist. I'd highly suggest you make sure that the requested URL contains image dimensions you support.
/images/get/200x200/1234.jpg
A malicious person could start requesting random URLs, always altering the height & width of the image. This would cause your server some serious issues b/c it will be sitting there, essentially under attack, generating images of sizes you do not support.
/images/get/0x1/1234.jpg
/images/get/0x2/1234.jpg
...
/images/get/0x9999999/1234.jpg
/images/get/1x1/1234.jpg
...
etc
Here's a random snip of code illustrating this:
<?php
$pathOnDisk = getImageDiskPath($_SERVER['REQUEST_URI']);
if(file_exists($pathOnDisk)) {
// send header with image mime type
echo file_get_contents($pathOnDisk);
exit;
} else {
$matches = array();
$ok = preg_match(
'/\/images\/get\/(\d+)x(\d+)\/(\w+)\.jpg/',
$_SERVER['REQUEST_URI'], $matches);
if(! $ok) {
// invalid url
handleInvalidRequest();
} else {
list(, $width, $height, $guid) = $matches;
// you should do this!
if(isSupportedSize($width, $height)) {
// size is supported. all good
// generate the resized image, save it & output it
} else {
// invalid size requested!!!
handleInvalidRequest();
}
}
}
// snip
function handleInvalidRequest() {
// do something w/ invalid request
// show a default graphic, log it etc
}
?>
Seems great post, but my problem still remains unsolved. I dont have access to htaccess in my host provider, so no question of apache tweaking. Is there really a way to set cace-control header for images?
Your approach seems quite reasonable - I would add that some mechanism should be put into place to check that the date the cached version was generated was after the last modified timestamp of the original (source) image file and if not regenerate the cached/resized version. This will ensure that if an image is changed by the designers the cache will be updated appropriately.
That sounds like a solid way to do it. The next step may be to go beyond PHP/MySQL.
Perhaps, tweak your headers:
If you're using PHP to send MIME types, you might also use 'Keep-alive' and 'Cache-control' headers to extend the life of your images on the server and take some of the load off of PHP/MySQL.
Also, consider apache plugin(s) for caching as well. Like mod_expires.
Oh, one more thing, how much control do you have over your server? Should we limit this conversation to just PHP/MySQL?
I've managed to do this simply using a redirect header in PHP:
if (!file_exists($filename)) {
// *** Insert code that generates image ***
// Content type
header('Content-type: image/jpeg');
// Output
readfile($filename);
} else {
// Redirect
$host = $_SERVER['HTTP_HOST'];
$uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$extra = $filename;
header("Location: http://$host$uri/$extra");
}
Instead of keeping the file address in the db I prefer adding a random number to the file name whenever the user logs in. Something like this for user 1234: image/picture_1234.png?rnd=6534122341
If the user submits a new picture during the session I just refresh the random number.
GUID tackles the cache problem 100%. However it sort of makes it harder to keep track of the picture files. With this method there is a chance the user might see the same picture again at a future login. However the odds are low if you generate your random number from a billion numbers.
phpThumb is a framework that generates resized images/thumbnails on the fly. It also implements caching and it's very easy to implement.
The code to resize an image is:
<img src="/phpThumb.php?src=/path/to/image.jpg&w=200&h=200" alt="thumbnail"/>
will give you a thumbnail of 200 x 200;
It also supports watermarking.
Check it out at:
http://phpthumb.sourceforge.net/