Building a PHP Image Proxy Script

 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:

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.
Previous
Next Post »

1 comments:

Click here for comments
quantpabis
admin
March 5, 2022 at 4:02 PM ×

Micro 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

Congrats bro quantpabis you got PERTAMAX...! hehehehe...
Reply
avatar