If you want users to upload files to your server you need to do a couple of security checks before you actually move the uploaded file to your web directory.

The uploaded data:

This array contains user submitted data and is not information about the file itself. While usually this data is generated by the browser one can easily make a post request to the same form using software.

$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];

Exploiting the file name

Normally the operating system does not allow specific characters in a file name, but by spoofing the request you can add them allowing for unexpected things to happen. For example, lets name the file: ../script.php%00.pngTake good look at that filename and you should notice a couple of things.

  1. The first to notice is the ../, fully illegal in a file name and at the same time perfectly fine if you are moving a file from 1 directory to another, which we’re gonna do right?
  2. Now you might think you were verifying the file extensions properly in your script but this exploit relies on the url decoding, translating %00 to a null character, basically saying to the operating system, this string ends here, stripping off .png off the filename.

So now I’ve uploaded script.php to another directory, by-passing simple validations to file extensions. It also by-passes .htaccess files disallowing scripts to be executed from within your upload directory.


Getting the file name and extension safely

You can use [pathinfo()](<http://php.net/manual/en/function.pathinfo.php>) to extrapolate the name and extension in a safe manner but first we need to replace unwanted characters in the file name:

// This array contains a list of characters not allowed in a filename
$illegal   = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\\\", "|", "?", "*", " "]);
$filename  = str_replace($illegal, "-", $_FILES['file']['name']);

$pathinfo  = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename  = $pathinfo['filename']  ? $pathinfo['filename']:'';

if(!empty($extension) && !empty($filename)){
  echo $filename, $extension;
} else {
  die('file is missing an extension or name');
}

While now we have a filename and extension that can be used for storing, I still prefer storing that information in a database and give that file a generated name of for example, md5(uniqid().microtime())

+----+--------+-----------+------------+------+----------------------------------+---------------------+
| id | title  | extension | mime       | size | filename                         | time                |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| 1  | myfile | txt       | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+