There is a fine line between useful and invasive. Today, I'm going to demonstrate something that can be used for either purpose: A PHP script that proxies images from a folder outside of your webroot folder.
But before we get to that, let's examine why we might want a PHP proxy in the first place:
When a user uploads a file, we way mish to give it a different file name, internally, than the one users see when they access it.
When a user uploads a file, we may wish to encrypt or encode it on the filesystem to ensure it never gets executed.
We may wish to collect usage statistics on where the file is linked from, to determine hotlinking and whatnot.
And also serve an alternative image for known offending websites
We may wish to serve a random image from a collection (special case, not part of this example).
We may wish to use unique image URLs to determine someone's IP address to track them down (for example, to track down the identity of a cyber-bully using a bit of social engineering).
In my examples, I will use a setup that looks something like this on an nginx webserver with php5-fpm and PDO-sqlite enabled:
For starters, we are going to create an nginx rewrite rule for the site1.com configuration (you can do this with Apache too) that looks like this:
That is to say, when someone accesses http://site.com/uploads/file.gif they are really accessing /var/www/site.com/public_html/myUploadProxy.php with the name=file.gif parameter passed to the script. Do you follow me so far?
Now, at this point, we want to serve images from /var/www/site.com/uploaded. All requests to the uploads folder are being redirected toward /var/www/site.com/public_html/myUploadProxy.php so all we have to do is read data from the correct folder and return it to the user, and we will have a simple image proxy script.
This script first determines that the file exists, determines its MIME type, and then outputs the file header and body to the user. Pretty simple, but our webserver could do that without having to slow things down by passing requests and image data through PHP. Why add any overhead if we're not going to benefit from it?
Here's a beefier, more secure image proxy. The only feature it's lacking is usage statistics and user metadata harvesting, because I feel if you're going to be a data leech and help spy on innocent end users, you can learn to do it without my help.
Changes made (and assumptions in the design):
- The filesystem does not know the user-supplied filename; instead, random hashes were used for actual storage and an sqlite database holds all of the metadata
- The files were gzip compressed to save space
- File type is restricted by a whitelist; anything that fails to match the allowed types are returned as text/plain
- Files are round-robined through 10 subdirectories in the uploaded files directory to increase filesystem performance (which helps to reduce DoS attack vectors).
But before we get to that, let's examine why we might want a PHP proxy in the first place:
When a user uploads a file, we way mish to give it a different file name, internally, than the one users see when they access it.
When a user uploads a file, we may wish to encrypt or encode it on the filesystem to ensure it never gets executed.
We may wish to collect usage statistics on where the file is linked from, to determine hotlinking and whatnot.
And also serve an alternative image for known offending websites
We may wish to serve a random image from a collection (special case, not part of this example).
We may wish to use unique image URLs to determine someone's IP address to track them down (for example, to track down the identity of a cyber-bully using a bit of social engineering).
In my examples, I will use a setup that looks something like this on an nginx webserver with php5-fpm and PDO-sqlite enabled:
root@webserver:/var/www# ls
global_icons
site.com
site2.com
site3.com
root@webserver:/var/www# ls site.com
cli_scripts
files.db
includes
public_html
uploaded
For starters, we are going to create an nginx rewrite rule for the site1.com configuration (you can do this with Apache too) that looks like this:
rewrite ^/uploads/([^/]+)$ /myUploadProxy.php?name=$1;
That is to say, when someone accesses http://site.com/uploads/file.gif they are really accessing /var/www/site.com/public_html/myUploadProxy.php with the name=file.gif parameter passed to the script. Do you follow me so far?
Now, at this point, we want to serve images from /var/www/site.com/uploaded. All requests to the uploads folder are being redirected toward /var/www/site.com/public_html/myUploadProxy.php so all we have to do is read data from the correct folder and return it to the user, and we will have a simple image proxy script.
<?php
define('BASEDIR', '/var/www/site.com/uploaded');
$name = str_replace('/', '', $_GET['name']); // Filter out LFI
if(preg_match('/(http|https|ftp|irc):/', $name)) {
header("Location: /404.html"); exit;
// 404 Not Found is the best way to handle this
}
if(!file_exists(BASEDIR."/{$name}")) {
header("Location: /404.html"); exit;
// File doesn't exist; you can do more flexible stuff here, like having a 404 image fallback
}
// Image found at this point. Let's continue by creating a finfo resource to determine mime types
$f = finfo_open(FILEINFO_MIME, "/usr/share/misc/magic");
$type = finfo_file(BASEDIR."/{$name}");
// Now let's pass the header so the browser knows what we're getting back
header("Content-Type: {$type}; filename=\"{$name}\"");
// Then let's output the data stored in the file to the user and close up shop for this request :)
echo file_get_contents(BASEDIR."/{$name}");
exit;
?>
This script first determines that the file exists, determines its MIME type, and then outputs the file header and body to the user. Pretty simple, but our webserver could do that without having to slow things down by passing requests and image data through PHP. Why add any overhead if we're not going to benefit from it?
Here's a beefier, more secure image proxy. The only feature it's lacking is usage statistics and user metadata harvesting, because I feel if you're going to be a data leech and help spy on innocent end users, you can learn to do it without my help.
Changes made (and assumptions in the design):
- The filesystem does not know the user-supplied filename; instead, random hashes were used for actual storage and an sqlite database holds all of the metadata
- The files were gzip compressed to save space
- File type is restricted by a whitelist; anything that fails to match the allowed types are returned as text/plain
- Files are round-robined through 10 subdirectories in the uploaded files directory to increase filesystem performance (which helps to reduce DoS attack vectors).
<?php
define('BASEDIR', '/var/www/site.com/uploaded');
$name = str_replace('/', '', $_GET['name']); // Filter out LFI
if(preg_match('/(http|https|ftp|irc):/', $name)) {
header("Location: /404.html"); exit;
// 404 Not Found is the best way to handle this
}
if(!file_exists(BASEDIR."/{$name}")) {
header("Location: /404.html"); exit;
// File doesn't exist; you can do more flexible stuff here, like having a 404 image fallback
}
// Image found at this point. Let's continue by creating a finfo resource to determine mime types
$f = finfo_open(FILEINFO_MIME, "/usr/share/misc/magic");
$type = finfo_file(BASEDIR."/{$name}");
// Now let's pass the header so the browser knows what we're getting back
header("Content-Type: {$type}; filename=\"{$name}\"");
// Then let's output the data stored in the file to the user and close up shop for this request :)
echo file_get_contents(BASEDIR."/{$name}");
exit;
?>
Further Considerations / Ways to Improve this Design
Encrypt the filedata with AES-256-CTR, using a random key and IV (stored in the database), signed with HMAC-SHA-256 by a PHP constant to detect errors before serving to the end user
Proxy requests through CURL to another device on the local network that store all of the files
Track file usage (access frequency over time) with the database
Cache the most popular files with APC
Rate-limit large files that aren't cached to prevent DoS attacks
Incorporate user-level access controls for certain files so only authenticated users who uploaded the file or had it shared with them through application logic can download the file later
If you need :
Simple PHP Image Proxy Script
More Secure PHP Image Proxy Script
If anyone has any other suggestions, feel free to comment below. And do not forgot to share our articles.
1 comments:
Click here for commentsMicro Titanium trim from tepid - TiGIA-ART
Micro Titanium ford escape titanium 2021 trim from tepid. A few titanium network surf freely months ago I was working titanium solvent trap monocore on a titanium teeth k9 new micro titanium trim with an extra base to make a light dental implants weight
ConversionConversion EmoticonEmoticon