10

I've been reading up on PHP file upload security and a few articles have recommended renaming the files. For example, the OWASP article Unrestricted File Upload says:

It is recommended to use an algorithm to determine the filenames. For instance, a filename can be a MD5 hash of the name of file plus the date of the day.

If a user uploads a file named Cake Recipe.doc is there really any reason to rename it to 45706365b7d5b1f35?

If the answer is yes, for whatever reason, then how do you keep track of the original file name and extension?

Nate
  • 26,164
  • 34
  • 130
  • 214
  • 3
    usually you keep track of files by putting the path and info about the file into a database table. As for renaming i have only done renaming to make sure the filenames are OS friendly. For instance `Cake Recipe.doc` has a space in it, linux doesnt really like spaced filenames on the commmand line so if you needed to do command line functions on the file you would have to wrap it in the correct quotes if you didnt rename it. – Patrick Evans Jul 25 '13 at 18:25
  • 1
    You can store the original name and extension in database along with the new name at the time of uploading –  Jul 25 '13 at 18:26
  • 2
    consider a poorly designed system that'd allow someone to upload `.php` files. If you put the files with the user's provided name within your site's document root, that user can now COMPLETELY take over your server, because they can upload and EXECUTE arbitrary code on your server. – Marc B Jul 25 '13 at 18:29

3 Answers3

23

To your primary question, is it good practice to rename files, the answer is a definite yes, especially if you are creating a form of File Repository where users upload files (and filenames) of their choosing, for several reason:

  1. Security - if you have a poorly written application that allows the download of files by name or through direct access (it's a horrid, but it happens), it's much harder for a user, whether maliciously or on purpose, to "guess" the names of files.
  2. Uniqueness -- the likelihood of two different people uploading a file of the same name is very high (ie. avatar.gif, readme.txt, video.avi, etc). The use of a unique identifier significantly decreases the likelihood that two files will be of the same name.
  3. Versioning -- It is much easier to keep multiple "versions" of a document using unique names. It also avoids the need for additional code to parse a filename to make changes. A simple example would document.pdf to document(1).pdf, which becomes more complicated when you don't underestimate users abilities to create horrible names for things.
  4. Length -- working with known filename lengths is always better than working with unknown filename lengths. I can always know that (my filepath) + (X letters) is a certain length, where (my filepath) + (random user filename) is completely unknown.
  5. OS -- the length above can also create problems when attempting to write extremely random/long filenames to a drive. You have to account for special characters, lengths and the concerns for trimmed filenames (user may not receive a working file because the extension has been trimmed).
  6. Execution -- It's easy for the OS to execute a file named .exe, or .php, or (insert other extension). It's hard when there isn't an extension.
  7. URL encoding -- Ensuring the name is URL safe. Cake Recipe.doc is not a URL safe name, and can on some systems (either server or browser side) / some situations, cause inconsistencies when the name should be a urlencoded value.

As for storing the information, you would typically do this in a database, no different than the need you have already, since you need a way to refer back to the file (who uploaded, what the name is, occassionally where it is stored, the time of upload, sometimes the size). You're simply adding to that the actual stored name of the file in addition to the user's name for the file.

The OWASP recommendation isn't a bad one -- using the filename and a timestamp (not date) would be mostly unique. I take it a step further to include the microtime with the timestamp, and often some other unique bit of information, so that a duplicate upload of a small file couldn't occur in the same timeframe -- I also store the date of the upload which is additional insurance against md5 clashes, which has a higher probability in systems that store many files and for years. It is incredibly unlikely that you would generate two like md5s, using filename and microtime, on the same day. An example would be:

$filename = date('Ymd') . '_' . md5($uploaded_filename . microtime());

My 2 cents.

Martin
  • 22,212
  • 11
  • 70
  • 132
Jacob S
  • 1,693
  • 1
  • 11
  • 12
4

When I upload files I use PHP's unique_id() function for the filename that is stored on the server (and I preserve the file extension since it makes it easier for me when I am looking at all the files in the storage directory via the local file system).

I save the file outside of the website file system (aka you can never browse directly to the files).

I always use php's move_uploaded_file() function to save the file to the server.

I store the original filename, the path/filename where it is stored, and any other project related information you might need about who uploaded it, etc in a database.

In some of my implementations I also create a hash of the file contents and save that in the database too. Then with other uploaded files look in the database to see if I have a copy of that exact file already stored.

Some code examples:

The form:

 form method="post" enctype="multipart/form-data" action="your_form_handler.php">



<input type="file" name="file1" value="" />



 <input type="submit" name="b1" value="Upload File" />

 </form>

The form handler:

 <?php

 // pass the file input name used in the form and any other pertinent info to store in the db, username in this example
_process_uploaded_file('file1', 'jsmith');

exit;



function _process_uploaded_file($file_key, $username='guest'){    
    if(array_key_exists($file_key, $_FILES)){
        $file = $_FILES[$file_key];
        if($file['size'] > 0){
            $data_storage_path = '/path/to/file/storage/directory/';
            $original_filename = $file['name'];
            $file_basename     = substr($original_filename, 0, strripos($original_filename, '.')); // strip extention
            $file_ext          = substr($original_filename, strripos($original_filename, '.'));
            $file_md5_hash     = md5_file($file['tmp_name']);
            $stored_filename   = uniqid();
            $stored_filename  .= $file_ext;                        
            if(! move_uploaded_file($file['tmp_name'], $data_storage_path.$stored_filename)){
                 // unable to move,  check error_log for details
                 return 0;
            }
            // insert a record into your db using your own mechanism ...
            // $statement = "INSERT into yourtable (original_filename, stored_filename, file_md5_hash, username, activity_date) VALUES (?, ?, ?, ?, NOW())";

            // success, all done
            return 1;
        }
    }    
    return 0;
}

?>

Program to handle download requests

 <?php

    // Do all neccessary security checks etc to make sure the user is allowed to download the file, etc..

    // 

    $file = '/path/to/your/storage/directory' . 'the_stored_filename';
$filesize = filesize($file);
header('Content-Description: File Transfer');
header("Content-type: application/forcedownload");
header("Content-disposition: attachment; filename=\"filename_to_display.example\"");
header("Content-Transfer-Encoding: Binary");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header("Content-length: ".$filesize);
ob_clean();
flush();
readfile("$file");
exit;

If you want to present the download in the same page that the user is requesting it from then look at my answer to this post: Dowloading multiple PDF files from javascript

Community
  • 1
  • 1
Drew
  • 4,215
  • 3
  • 26
  • 40
1

There is a good reason you need to rename uploaded file and it is, if two upload same file, or files with same name, the latter file will replace the former file which is not favourable.

you can use hashing algos like

$extensions =  explode(".",$file-name);
$ext = $extensions[count($extensions)-1]; 
$file-name = md5($file-name .$_SERVER['REMOTE_ADDR']) .'.' .$ext;

then you can save details of filename, hashed filename, uploader details, date, time to keep track of files

Minhaz
  • 937
  • 1
  • 10
  • 25