Correct way to upload files in PHP Mailer - php

Hi can someone help with this bit of code I am trying use to handle and test my file uploads in PHPMailer? It basically checks that the file is correct and then renames the file name by using the users name plus field name and a date and time. There are multiple files but I am not using a multi uploader but instead separate fields so I can keep a track of which file is which.
The script seems to work and there are no errors in the php error logs but I'm told this is a security flaw in my previous post and that my "pathinfo call should be testing the path to the tmp_name of the actual file and NOT the given original name. This is a serious security flaw."
Unfortunately I'm not sure which of the 2 usages of pathinfo is wrong. If I change $file["name"] to $file["tmp_name"] for $imageFileExt then I don't get a file extension and if I change $file["name"] to $file["tmp_name"] on $imageFileType then I just get wrongfile error. Any help would be much appreciated. Thanks.
foreach ( $_FILES as $key => $file ) {
//get the file extension
$imageFileExt = strtolower( pathinfo( $file["name"], PATHINFO_EXTENSION ) );
//change the name of each file in $_FILES array to
//the persons name plus file field plus date plus time plus file extension
//such as :joe_bloggs_bank_statement_1_9_10_21_10_55.jpg
//and joe_bloggs_pay_slip_1_9_10_21_10_55.jpg
$file['name'] = clean($_POST['appName']). "_" . $key . "_" . $dateKey . "." . $imageFileExt;
// get the file type
$target_file = $target_dir . basename($file["name"]);
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
// get file size
$check = getimagesize($file["tmp_name"]);
if($check === false) {
$fileMessage .= $key."=noimage,";
$uploadOk = 0;
}
// Allow certain file formats
else if($imageFileType !== "jpg" && $imageFileType !== "png" && $imageFileType !== "jpeg"
&& $imageFileType !== "gif" ) {
$fileMessage .= $key."=wrongfile,";
$uploadOk = 0;
}
// Check if file already exists
else if (file_exists($target_file)) {
$fileMessage .= $key."=fileexists,";
$uploadOk = 0;
}
// Check file size
else if ($file["size"] > 20000000) { //20mb
$fileMessage .= $key."=toobig,";
$uploadOk = 0;
}
// creates a set of links to the uploaded files on the server
// to be placed in the body of the email (the files themselves do not get attached in the email
$fileString .= strtoupper($key).": <a href='example.com/uploads/".$file['name']."'>".$file['name']."</a><br>";
}

I think you may be confused by the distinction between these things. The file itself is a bunch of bytes provided by the user. Its filename is metadata about that file, also supplied by the user. The tmp_name is also metadata about the file, but is safe because it is not supplied by the user.
At no point are you using move_uploaded_file() or is_uploaded_file(). These are necessary to validate that the uploaded files are real uploaded files that have been handled correctly by PHP before your script is run. That's the very first thing you should do before trusting anything else in $_FILES.
The reason it's important to not trust the name property is that filenames can be used as an attack vector, for example by including path traversal sequences (such as ../), SQL escapes ('), or characters that might do unexpected things if used in shell contexts (\, >, etc). The tmp_name is generated by PHP itself and does not contain user-supplied data, though you need to use the functions mentioned above to ensure that this is real data and not also injected
by the user.
Similarly, the filename can't be relied upon to tell you accurately what type a file is – search for "polyglot files" for why that can be a problem.
You don't show what's in your clean() function, so we can't say whether what it does is effective.
In your previous question I referred you to the PHPMailer send file upload example. This shows how to handle an upload safely. For example, it takes whatever name is provided and hashes it, producing a string that is guaranteed to be safe from user-supplied data that is not.

Related

Are There Any Security Issues In The Following File Upload Script - PHP

I've looked at a number of the answers to StackOverflow questions on safely uploading images with PHP. I've put this following script together with explainers and wanted to know if this is missing anything. My only/main concern is I can't seem to find much info on stripping out harmful code from the image itself, although this is partly covered in the code.
A couple of SO answers touch on the GD image functionality but they don't really give any good code example cases and because I'm new to php I can't quite seem to wrap my head around how to use this (in terms of creating a new version of the image).
Note: This images in this code go to an '/images' directory, but on the live site they will go into a subdomain called 'images' which is outside the public folder and which will serve static files only (no PHP, Perl etc). The short_open_tag will be turned off in the php.ini file.
The files are selected with a file input type with the name 'profile-image'.
The following code is split into its component parts - the first part is the if/isset statements that check that a submit button called 'submit-profile-image' has been clicked, and the files have been uploaded into memory in the ['tmp_name'] key of the $_FILES superglobal:
if(isset($_POST['submit-profile-image'])) {
$allowed = ['jpeg', 'jpg', 'png'];
if($_FILES['profile-image']['error'] === 0 ) {
// THE DIFFERENT CHECKS IN THE MAIN CODE BELOW GO HERE
} else {
$error[] = "Image failed to load please try again";
}
}
This following code all goes inside the 2nd if statement shown above - I've broken it down to show what it is meant to acheive:
Set variable names of temp upload file and file input name
$profileImageName = $_FILES['profile-image']['name'];
$temp = $_FILES['profile-image']['tmp_name'];
Explode string to split the file name and file extension
$ext = explode('.', $profileImageName);
$ext = strtolower(end($ext));
Completely rename file, and only keep the file extension from the original file:
$file = uniqid('', true) . time() . '.' . $ext;
Sanitize string for extra safety (probably not needed)
$file = filter_var($file, FILTER_SANITIZE_STRING);
$file = strtolower($file);
Check the file extention matches the allowed array of file extensions:
if (!in_array($ext, $allowed)) {
$error[] = "File type must be in the jpg / jpeg or png format";
}
Check MIME type using the getImageSize() function which is more reliable than the 'type' key found in the standard $_FILES superglobal:
$getImageSizeMime = getImageSize($temp);
if(isset($getImageSizeMime['mime']) == false) {
$error[] = "Not a recognised MIME type";
} else {
$getImageSizeMime = $getImageSizeMime['mime'];
}
Make sure the MIME type matches the file extension:
if (in_array($ext, $allowed) != $getImageSizeMime) {
$error[] = "Mime type must be of the jpg / jpeg or png format";
}
Inspect contents of image file itself:
$cleanUpload = file_get_contents($temp) ;
Disallow if file contents contain php or script:
if(preg_match('/(<\?php\s)/', $cleanUpload)) {
$error[] = "Image data cannot contain php script tags";
}
if(preg_match('/script/', $cleanUpload)) {
$error[] = "Image data cannot contain javascript tags";
}
Sanitise file contents of HTML tags
$cleanUpload = filter_var($cleanUpload, FILTER_SANITIZE_STRING);
Move uploaded file if none of the above errors are present
if (!isset($error)) {
if(in_array($ext, $allowed)) {
move_uploaded_file($temp, 'images/' . $file);
}
}
Any input on any security issues missed or any extra checks on the image file itself would be hugely appreciated - particularly on how to duplicated the image to only keep image data using the GD library if possible/necessary? Some of the answers on StackOverflow are very old and seem to feature methods that aren't seen as the most up to date either (which I've avoided in this question).
It would be really good to see if there are any PHP methods for checking image files themselves and removing potentially dangerous code.

Replace save files if from same User

I have a directory called resale_certificates where I save files to but encrypt the name using parts of their email and codes assigned to them.
NOTE: The encryption is the same every time but unique to each user!
When they upload image.png it will save the file to theirEncrypt.png
If they upload another image.png it will replace theirEncrypt.png
However when they upload image.jpg now there will be theirEncrypt.jpg and theirEncrypt.png in the resale_certificates directory.
What is the best way to handle this? I'm looking for advice and open to changing how I'm saving it or tricks I could do to prevent this!
Thank You!
Well, you could use an image library to transform their uploaded image to whatever format you want, i.e. if they upload a .JPG you can use image libraries like Imagick or GD to output a .PNG file and upload those.
However, if you don't mind either the .JPG or .PNG ( or .GIF for that matter) you can scan the directory with PHP to look for all files ( can be really intensive though! ) to look for files with the name given.
For example:
<?php
foreach( scandir('/path/to/resale_certificates/') as $file ){
if( $file != '.' && $file != '..'){
// explode so we remove the last extension path ( not type safe ! )
$arguments = explode('.', $file);
// store the last part
$ext = end($arguments);
// pop the extension from the $arguments array so we are left
// with whatever was left
array_pop($arguments);
// concatenate the $arguments into a single string again
$filename = implode('.', $arguments);
// now we can check the filename again
if( $filename == $theirEncrypt){
unlink('/path/to/resale_certificates/' . $filename . '.' . $ext);
}
}
}
edit:
the $file is a string from the $files array returned by the scandir(); function. The single and double dot are a ways to navigate to the current (.) and the parent (..) directory and are therefore symlinks. Another option would be to check if the $file is actually a file. You could replace the comparison line with a is_file('/path/to/resale_certificates/' . $file) to check if it's a file or a symlink ( like the . and the .. ) but it's even more intensive then to check string comparison. In your usecase it is not neccesary.
On a related note, this is quite intensive, depending on the number of clients and certificates you have, you could, as an alternative, store the filename to storage (i.e. database or something similiar) and just unlink the file find there, this would save you to iterate over each file and simply unlink the file directly.
If you know a name of previously uploaded image then you can do the following before saving a new image:
<?php
$previousImageName = 'theirEncrypt.png';
unlink(APP_DIR . "/resale_certificates/" . $previousImageName);

why tmp_name is used in this example

I am quite new to php.I was going through an example on file uploading.Here inside getimagesize() function $_FILES['file']['temp_name'] is used.and when i echoed $_FILES['file']['temp_name'] it shows the following output:
C:\xampp\tmp\phpDE4B.tmp
my questions are:
1.why use tmp_name instead of original name inside getimagesize() function.
2.For what reason this tmp_name is created ?
3.How it is generated?
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if($check !== false) {
echo "File is an image - " . $check["mime"] . ".";
$uploadOk = 1;
} else {
echo "File is not an image.";
$uploadOk = 0;
}
}
The temporary file is generated as part of the file upload process, handled directly between PHP and the web server.
A temp file is used so that userland code can move the file to its final destination folder on the server - the final destination folder isn't information that can be passed in the http request, or handled by the web server, because it depends entirely on your application. If the file isn't moved to its final destination folder, then the temporary file will be deleted automatically at the end of the request
In this case, the code is part of a validation process to ensure that the file is what it claims to be before moving it to its final destination folder (assuming that it is a valid file).
When some file is uploaded to the server, it is placed in temporary directory to work with it. In order to not have collisions in names temporary name is assigned.
1) You need to pass the path to file to getimagesize(), because you can want to work not only with saved files.
2) Tmp_name is created to avoid collisions, but you can get original name if you need.
3) Just some random generated name inside of a temp directory.

How do I restrict my uploaded files to accept only zip files ?

Good morning guys. I have created two php files that successfully upload files to my server. One file is the visual part for my website called upload.php and the other is the upload file part called upload_file.php.
the code to upload my files is
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
This works perfectly however it lets me upload any file type. So since I want to only allow zipped folders i tried this if statement.
if($type=="application/zip" ){
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";}
else{
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
where $type=$_FILES['file']['type'];
But now it doesnt let me upload any files not even zipped ones. So what do i need to put in my if statement to only allow zipped folders to be upload? And if your really good guys what do i need to put in my if statement to allow only zipped foleders with the naming convention of USERINITIAL.DATE.TIME or USERINITIAL/DATE/TIME or can this not be done?
You can use this solution
$fileName = strtolower($fileName);
$allowedExts = array('zip');
$extension = explode(".", $fileName);
$extension = end($extension);
if(in_array($extension, $allowedExts))
{
//move file to custom folder
}
IMPORTANT *
Never not use from mime time for identification file type,because it bypass with tamper data.
Best way:
Move your all uploaded file into out of public_html,and Always rename file name,when you want upload this.
And so,save uploaded file name into database,and read file from one php file,for example:
read.php?id=5
in your read.php file,you should get id number and search on database for it,then,return file name from db and download or read this file with read.php file.
Due to some discussion on this thread, heres a little bonus info.
Generally speaking, it's really, really hard to determine if a file is actually the kind of file we want. You can check the mime type, which can be modified by the client. You can check the file extension, which can also be modified by the client- Vice versa.
You can even check the first few lines of a file, which typically contains some sort of header, explaining what kind of file we'r handling. But still then, the file might be modified by some evil genius making the executing program buffer overflow or exploits some library used, to open/view/taste/throw the file.
Lets check both file extension and mime.
First, the extension.
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$isZipExtension = ( $extension == "zip" ? true : false );
On your "$_FILES" array, you have a index called "Type".
Now, you would like to restrict the upload to only accept Zip files.
Theres a few types defining a potential zip file. So lets create an array.
$zipTypes = array('application/zip', 'application/x-zip-compressed',
'multipart/x-zip', 'application/x-compressed');
Now, lets check if the type uploaded, is part of the array.
$isZipFile = in_array( $_FILES['file']["type"], $zipTypes );
If, the file is in the array, do your upload process.
if( $isZipFile && $isZipExtension) {
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";
} else {
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
All together
$zipTypes = array('application/zip', 'application/x-zip-compressed',
'multipart/x-zip', 'application/x-compressed');
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$isZipExtension = ( $extension == "zip" ? true : false );
$isZipFile = in_array( $_FILES['file']["type"], $zipTypes );
if( $isZipFile && $isZipExtension) {
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";
} else {
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
Hope it helps.
Ironically, you should never use the type key to check the type of file being uploaded. That's because the value of that key is set by the client and can be trivially spoofed.
What you should be doing instead is checking the file extension (which at least makes sure that no well-configured program on your server will treat the upload in an unexpected manner):
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$allowed = ['zip'];
if (in_array($ext, $allowed)) {
// process the file
}

How to generate unique filenames for uploaded pictures

I store uploaded pictures in a folder while I store the path and filename in a database. Please how best can I generate unique filenames to avoid name conflict in the folder? Thanks
You've tagged your question with PHP and MySQL.
You could use PHP's uniqid function to generate a unique string
You could use a MySQL table to store information about the images, and use an AUTO_INCREMENT key in that table to give you a unique integer which you could use as the file name
Do you have any more specific requirements/goals? If not, I imagine either of those solutions should work for you.
Probably the closest unique value you have is the database file ID. But if you save your files in different tables this will no longer be the case (ex: ArticleFiles and NewsFiles tables). In this case you can use separate directories for separate tables, or prefixing your files.
You may also be required to upload files to the same server location from different host names. In this case you can prefix the file name with the host name used for upload.
People tend o use the time() function, but that's mostly informative, as it doesn't protect you against concurrent uploads. It does help you in finding the files on the disk easier, by ordering. For the same reason you may include the original extension in the file name.
In the end, use the shortest and most informative unique value or composed value you can get, like
$file = "{$HOST}.{$TYPE}.{$TIMESTAMP}.{$DATAID}.{$EXT}.uploaded";
However, be very careful with access rights on your uploads folder, as it's a serious security risk. Do not allow execution rights, try placing it outside of the document root, check uploads for possible dangerous files, etc.
You can use
$image_name=('Your folder path/'. time () .'.'$extension);
for get any image extension
$extension = getExtension($filename);
function getExtension($str) {
$i = strrpos($str,".");
if (!$i) { return ""; }
$l = strlen($str) - $i;
$ext = substr($str,$i+1,$l);
return $ext;
}
is little complex but its make your work easy , you can use this extension for validation on your file type
like
if (($extension != "jpg") && ($extension != "jpeg") && ($extension != "png") && ($extension != "gif"))
{
echo '<h1>Unknown extension!</h1>';
$errors=1;
}else
//your code to save image in folder.
Have you considered storing the data in the database ?
If you are able to; you will get rid of the need to have unique file name, and the problems associated with the files & database being out of synch.

Categories