简体   繁体   中英

Does this PHP function protect against file traversal?

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.

ie 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);
            }
        }
    }
}

Better idea: have the user supply the MD5, and construct the filename yourself. That way you don't have to do all sorts of crazy checking for user input versus filenames - you can just ensure that the MD5 is a 40-character string of [0-9a-f] only, and then you're good.

I could traverse to any directory, but I would be limited to file names which start with my user id.

consider

$file_name = '/./.\\1234_anything.anything';
         $file_name = str_replace ('..', '', $file_name);
         echo $file_name = str_replace ('/', '', $file_name);

and as far as file path seperators go, / and \\ are generally equivalent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM