I'm semi-new to PHP and I'm starting to dive into file downloading. I create .xlsx
and .csv
files using PHPExcel
and place them in a temp directory to be downloaded. I found a nice script for doing the download and I added some tweaks to it that I needed. The script is below. I've already read these posts:
Secure file download in PHP, deny user without permission ...and... Secure files for download ...and... http://www.richnetapps.com/the-right-way-to-handle-file-downloads-in-php/
download.php
<?php
/*====================
START: Security Checks
====================*/
//(1) Make user it's an authenicated/signed in user with permissions to do this action.
require("lib_protected_page.php");
//(2) Make sure they can ONLY download .xlsx and .csv files
$ext = pathinfo($_GET['file'], PATHINFO_EXTENSION);
if($ext != 'xlsx' && $ext != 'csv') die('Permission Denied.');
//(3) Make sure they can ONLY download files from the tempFiles directory
$file = 'tempFiles/'.$_GET['file'];
//ABOUT ITEM 3 - I still need to change this per this post I found....
/*
http://www.richnetapps.com/the-right-way-to-handle-file-downloads-in-php/
You might think you’re being extra clever by doing something like
$mypath = '/mysecretpath/' . $_GET['file'];
but an attacker can use relative paths to evade that.
What you must do – always – is sanitize the input. Accept only file names, like this:
$path_parts = pathinfo($_GET['file']);
$file_name = $path_parts['basename'];
$file_path = '/mysecretpath/' . $file_name;
And work only with the file name and add the path to it youserlf.
Even better would be to accept only numeric IDs and get the file path and name from a
database (or even a text file or key=>value array if it’s something that doesn’t change
often). Anything is better than blindly accept requests.
If you need to restrict access to a file, you should generate encrypted, one-time IDs, so you can be sure a generated path can be used only once.
*/
/*====================
END: Security Checks
====================*/
download_file($file);
function download_file( $fullPath )
{
// Must be fresh start
if( headers_sent() ) die('Headers Sent');
// Required for some browsers
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
// File Exists?
if( file_exists($fullPath) )
{
// Parse Info / Get Extension
$fsize = filesize($fullPath);
$path_parts = pathinfo($fullPath);
$ext = strtolower($path_parts["extension"]);
// Determine Content Type
switch ($ext) {
case "pdf": $ctype="text/csv"; break;
case "pdf": $ctype="application/pdf"; break;
case "exe": $ctype="application/octet-stream"; break;
case "zip": $ctype="application/zip"; break;
case "doc": $ctype="application/msword"; break;
case "xls": $ctype="application/vnd.ms-excel"; break;
case "xlsx": $ctype="application/vnd.ms-excel"; break;
case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
case "gif": $ctype="image/gif"; break;
case "png": $ctype="image/png"; break;
case "jpeg":
case "jpg": $ctype="image/jpg"; break;
default: $ctype="application/force-download";
}
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
//Now, the use of Cache-Control is wrong in this case, especially to both values set to zero, according to Microsoft, but it works in IE6 and IE7 and later ignores it so no harm done.
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=\"".basename($fullPath)."\";" );
//Note: the quotes in the filename are required in case the file may contain spaces.
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".$fsize);
ob_clean();
flush();
readfile( $fullPath );
}
else
die('File Not Found');
}
?>
My questions are...
- Are my security checks enough? I only want authenticated users with proper permissions to be able to download
.xlsx
and.csv
files from only thetempFiles
directory. But I've read that download-able files should be outside the webroot, why? With these checks I don't see why that would matter? - The
tempFiles
directory is forbidden if you type it in on the address bar (www.mySite.com/tempFiles), but if the user somehow guesses a filename (which would be difficult, they are long and unique) then they could type that in on the address bar and get the file (www.mySite.com/tempFiles/iGuessedIt012345.csv). So is there a way to not allow that (I'm running Apache), so they are forced to go through my script (download.php
)?
Thank you! Security is my number 1 concern so I want to learn every little thing I can about this before going live. Some of the example download scripts I've seen literally would let you pass in a php filename thus allowing people to steal your source code. FYI, I do clean up the tempFiles
directory fairly regularly. Just leaving files there forever would be a security issue.