Reputation: 2489
I am in the process of writing an application (php/laravel) that will allow users to upload images. Simple. No problem. In the past whenever I have written applications like this, where users can upload images but they should only be accessible by the user who uploaded them, I created a public 'uploads' directory within the home directory of the webserver. Within this uploads directory I included an index.html file that simply displayed a message: "The contents of this directory are hidden".
This kept users from seeing the contents of the files if they manually browsed to the uploads directory from their web browser. Then as an additional layer of "security" I would append an md5 hash of a random set of characters to the filename. Filenames would end up looking like:
my_image__5a73e7b6df89f85bb34129fcdfd7da12.png
This made it extremely unlikely that anyone would be able to guess a specific filename within the uploads directory.
Is this still the best way to go about implementing this type of functionality? Is there some gaping hole that I have overlooked in my process? For some reason it just doesn't feel right to have sensitive files sitting in the webroot...
Upvotes: 2
Views: 74
Reputation: 33068
I'd store all the images outside of the webroot. For each image, you'd have a record in a database which contains information about who can access that image, the name of the image, and the full path of the image.
To display the actual image, you'd need to create an additional image route which will reach into that directory and create an appropriate response. This is actually very easy if you add the intervention package.
Route::get('images/{imageName}', function($imageName) {
// Check database to ensure currently logged in user has acces to the image.
$image = DB::table('images')->where('name', $imageName)->where('user', Auth::user()->user_name)->first();
if(!is_null($image)) {
return Image::make($image->full_path)->response('png');
} else {
return Image::make('defaultNotAllowedImage.png')->response('png');
}
});
Now if they call images/someTestImage.png and someTestImage.png matches the name of the image and the user, it will return that image, otherwise it will return a default image.
Upvotes: 2
Reputation: 2218
Here's what I do:
The upload dir is OUTSIDE the web root. There's no index file.
I randomize filenames.
I use application logic to determine who can see what and then send image data to the browser:
I.e
public function images($dir, $image_filename)
{
$allowed_dirs = array(
//some directories I know files in for
//a small amount of security
}
//check if user is allowed to view the passed image
$user = $this->user;
$this->validate_user($user);
if (array_key_exists($dir, $allowed_dirs)) { //is $dir allowed?
$path = BASEPATH . $allowed_dirs[$dir];
$details = @getimagesize($path . '/' . basename($image_filename));
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Pragma: no-cache");
header('Content-Type: ' . $details['mime']);
@readfile($path . '/' . $image_filename); //send image data to browser
exit();
}
else {
exit();
}
}
Upvotes: 1