I have a URL which will serve a protected file to my user.
The file name is re-written by my application when the file is uploaded, regardless of the name, and stored in my database. So I know it will never contain a "/" or ".."
The filename is: "USER_ID"_"RANDOMMD5".FILE_EXT With "USER_ID" = current logged in user ID and "RANDOM MD5" exactly that.
i.e. 5_ji3uc237uckj92d0jf3932t09ut93f2.pdf
This is my function to serve the file:
function user_file($file_name = "")
{
if ($file_name)
{
// Ensure no funny business names:
$file_name = str_replace ('..', '', $file_name);
$file_name = str_replace ('/', '', $file_name);
// Check if the user is allowed to access this file
$user_id = substr($file_name, 0, strpos($file_name, "_"));
// now do the logic to check user is logged in, and user is owner of file
(($this->ion_auth->logged_in()) && (($user_id === $this->user->id))
{
// Serve file via readfile()
}
}
}
Question: Is this a secure way to ensure that there is no other way for the person to transverse directories, gain access to other files etc?
edit 1: ion_auth is my authentication library, and "$this->user->id" is the user id stored in my construct
edit 2: The user files are stored outside public_html - and thus only accessible via my application AFAIK
edit 3: My improved code, using the ideas from Amber below, taking into account the fact I need to accomodate for different file extensions, and I'm going to try and avoid a database hit:
function user_files($file_name = "")
{
// for security reasons, check the filename is correct
// This checks for a 32bit md5 value, followed by a single "." followed by a 3-4 extension (of only a-z)
if (preg_match('^[A-Za-z0-9]{32}+[.]{1}[A-Za-z]{3,4}$^', $file_name))
{
// Now check the user is logged in
if ($this->ion_auth->logged_in())
{
// Rewrite the request to the path on my server - and append the user_id onto the filename
// This ensures that users can only ever access their own file which they uploaded
// As their userid was appended to the filename during the upload!
$file = MY_SECURE_FOLDER.$this->user->id.'_'.$file_name;
// Now check if file exists
if (file_exists($file))
{
// Serve the file
header('Content-Type: '.get_mime_by_extension($file));
readfile($file);
}
}
}
}