Are There Any Security Issues In The Following File Upload Script - PHP - 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.

Related

Is verifying a file's extension enough to know that the file isn't hiding a virus (or something that can infiltrate my server)?

I'm developing a system where the user will be able to upload a ".docx" file. Is verifying it's extension enough to know that this ".docx" file isn't infected?
Here's my upload PHP code:
<?php
session_start();
include("connection.php");
include("functions.php");
// Just to validate the user
$user_data = check_login($con);
include("connectionPostsDB.php");
if (isset($_POST['submit'])){
$title = $_POST['title'];
$tag = $_POST['tag'];
$description = $_POST['description'];
$file = $_FILES['file'];
$fileName = $_FILES['file']['name'];
$fileTmpName = $_FILES['file']['tmp_name'];
$fileSize = $_FILES['file']['size'];
$fileError = $_FILES['file']['error'];
$fileType = $_FILES['file']['type'];
$fileExt = explode('.', $fileName);
$fileActualExt = strtolower(end($fileExt)); //here I get the actual file's extension (I hope xD)
$allowed = array('docx');
if(in_array($fileActualExt, $allowed)){
if($fileError === 0){
if($fileSize < 1000000){
$fileNameNew = uniqid('', true).".".$fileActualExt;
$fileDestination = '../imgs/posts/'.$fileNameNew;
move_uploaded_file($fileTmpName, $fileDestination);
$query = "INSERT INTO posts (title, descr, imgname, tag)
VALUES ('".$title."','".$description."','".$fileNameNew."','".$tag."')";
mysqli_query($postcon, $query);
echo 'File successfully uploaded';
}
else {
echo 'Your file is too big.';
}
}
else {
echo 'There was an error uploading your file.';
}
}
else {
echo 'This type of file not allowed.';
}
}
So, me checking for the file's extension is enough to prevent some user to put some php code in my server (or do something to get information from the server)?
No, just checking the extension isn't enough, however the main question is what you exactly mean by safe.
The code you've shown only stores the file on your server (under a server-generated name), and then publishes its existence in the database. Neither of those things themselves would be dangerous, even if the file contained the digital equivalent of the black plague.
But let's first take a step back; your SQL query is very much unsafe, though that has nothing to do with the uploaded files. You should read up on prepared statements as your entire site is wide open to SQL injection when you create queries this way.
Now, back on topic:
There are at least 2 things you could explore in order to become more relaxed about the .docx file being what it should be.
As far as i know, the docx format is really just a .zip file with some .xml files inside. PHP understands both those formats, so you could attempt to unzip it's contents, see if it contains the sorts of files you expect, including xml(s), and then see if those XML files are parse-able.
You could feed the uploaded file to a virusscanner
Neither of these methods is full proof, but they will both illuminate the situation a bit, allowing you (more specifically, your algorithm) to make a more informed decision.
Now as for what i ment with "what you exactly mean by safe". The actions you take here (merely renaming and storing the file and writing text to a database) are fundamentally pretty safe.
But i imagine there is someone or something else, on the other side of that database, that reads the same DB table, and allows someone to either open and or download that file. Now that action does actually have a lot of security implications.
Now if that database is only viewed through your application, then you could theoretically waive all responsibility and say "this file was anonymously uploaded by god knows who, treat it as if it arrived in your emails junk folder by unknown sender". But if you're taking any level of responsibility (be it implied or literal), you'll have to verify it to whatever degree you're comfortable with.
Nope, it's never enough just chekcing the file extension because we can set .docx extension to any file type.
We should also check many more things like:
Is the mime type of the file expected?
Is the file size reasonable enough. For example, if someone upload a 25 GB docx file, then even if it's a valid docx file your server might go down.
Ideally you shouldn't upload any file in any production server instead you should upload in any storage provider like s3 bucket of AWS. So that even if that file is executable it's not being executed in your server. It's uploaded into a different server that is well optimized to handle files.
And many more such things to consider.

Uploading a profile picture and displaying it

I want users to be able to upload a profile picture (which can be .jpg or .png) and I also want this to be displayed on their profile. I have written some code, based on sources I found here, on Stackoverflow and Google. However, it does not seem to work and I can't find my mistake.
This is the html
<form action="account_settings.php" method="POST">
<input type="file" name="profilePicture"><br><br>
<input type="submit" value="Change!">
</form>
This is how to uploaded file will be processed.
<?php
include ('inc/header.inc.php');
if(isset($_FILES["profilePicture"]["tmp_name"]) && isset($_FILES["profilePicture"]["name"])) {
$ext = pathinfo($_FILES['profilePicture']['name'], PATHINFO_EXTENSION);
$name = $_SESSION['user_login'];
$tmp_name = $_FILES["profilePicture"]["tmp_name"];
if($ext == 'png' || $ext == 'jpg') {
if (isset($tmp_name)) {
if(!empty($tmp_name)) {
$location = '../profielfotos/';
$full_name = $name.'.'.$ext;
if(move_uploaded_file($tmp_name, $location.$full_name)) {
echo 'Photo uploaded!';
}
Down here are just some else statements with error reports.
The code below is used to display the image. I have tested it by putting an image in the profile pictures folder and it did display the image. However, there is still a problem. People are allowed to upload .jpg or .png, how can I make the website display the picture (find the profile picture with the right extension).
I have put this code inside the src attribute of the <img>tag.
<?php if ($handle = opendir('profielfotos/')) {
$file = mysql_real_escape_string($_GET['u']);
echo 'profielfotos/'.$file.'.png';
}
closedir($handle);
I hope someone can help, thanks in advance!
ps. this is my first post ever on stack overflow :-D!
Since you are not storing any info about the file uploaded, you just have check which file exists, using he file_exists() method. See here:
http://php.net/manual/en/function.file-exists.php
So your code will become something like this (Not tested):
<?php if ($handle = opendir('profielfotos/')) {
$file = mysql_real_escape_string($_GET['u']);
if (file_exists('profielfotos/'.$file.'.png')) {
echo 'profielfotos/'.$file.'.png';
} else if (file_exists('profielfotos/'.$file.'.jpg')) {
echo 'profielfotos/'.$file.'.jpg';
}
}
closedir($handle);
You need to add the following to your form:
<form action="account_settings.php" method="POST" enctype="multipart/form-data">
Otherwise it won't allow a file upload as it expects only text.
This is totally insecure. Files uploaded by a user shall never ever be stored within the root of the web server.
Instead, put the files somewhere outside of the doc root.
Write a handler, which takes control of he files
check the mime type by checking the content, not the extension
have arbitrary names, not the name from the upload, that might interfer (imagine 5 people uploading a "profile.png")
let the handler deliver the image by an id ("...imagloader?file=4711"),
name of the file (and extension and location) is stored in a database (with the user record?)

PHP fileupload type check

Ok here is my code for uploading files
$ext_whitelist = array('pdf','doc','doc','mkv','mp4','mpg','mpeg','avi','flv','wma','ogg');
if(in_array($ext, $ext_whitelist))
{
$uniqid_file = uniqid('', true)."_".$file['name'];
$lokacija = $folder . "/" . $uniqid_file;
$encoded_uniqid_file = base64_encode($uniqid_file);
move_uploaded_file($file['tmp_name'], $lokacija);
$base_url= base_url("forms/fdownload/$encoded_uniqid_file/$path");
$form_data[$key] = "$uniqid_file ";
}
This checks file extension, so easy some could rename file, can someone help me to check file type proper?
Insted of a comment, I'll write a bit more as an answer.
Mimetype checking is a good thing if you want to know the type of the file, but it's not secure if you want to allow/deny the files at upload, because it's very easy to fake the mimetype.
Just try it, you can change it with a proxy or you can create a simple image, then add some php code at the end and rename it to .php. If you only check the mimetype, you can upload this .php file and run it on the server.
If you upload .jpg with php code in it, it's okay, the server won't push it through the php parser. (Except when you change the default configuration. (Apache: AddType, nginx: AddHandler )
There are some "secure" ways to check the uploaded files:
1. Check the extension and compare it to a whitelist.
This is the example in the question, but I'd like to write a complete solution. (A common mistake to check only the first think after the ., because there could be file names like: something.txt.php so always check the last postfix.)
$ext = array_pop(explode(".", $fileName));
$whitelist = array('pdf','doc','doc','mkv','mp4','mpg','mpeg','avi','flv','wma','ogg');
if (in_array($ext, $whitelist) {
//OK the extension is good, handle the upload.
} else {
//Wrong type, add error message.
}
If you use something like this, be careful and never allow extensions like .php and anything wich is in the server config.
2. Rename the file and drop the extension.
This is an another good way, but maybe you want to keep the original file name, the extension and the mimetype. You can store them in a database!
For this solution just take the original filename, add some random data (because if you upload into a single folder and you trie to upload something.jpg 2 time that would be a bad idea), then store this.
For example:
$newName = sha1($fileName.time());
move_uploaded_file($file['tmp_name'], $uploadPath . $newName);
Because the file doesn't have an extension, the server wont try to run it. (But if it's for example an image it'll work in the browsers, because they use the mimetype to determine the type and we didn't changed that.)
You can use
perl-file-mimeinfo
Ex:-
$file_path = '/tmp/temp.jpg';
$mimetype = trim(shell_exec("/usr/bin/mimetype -bi ".escapeshellarg($file_path)));
$info = null;
if(strpos($mimetype, "video/")===0 || strpos($mimetype, 'x-flash-video') > 0){
$info = 'video';
}elseif(strpos($mimetype, "audio/")===0){
$info = 'audio';
}elseif(strpos($mimetype, "image/")===0){
$info = 'image';
}

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
}

File Uploading and Security

I'm currently writing an upload class for uploading images. I do extension checks to verify that the uploaded images are of the supported types, and the photos are always chmod(0664) when the uploaded file is copied to it's resting place. Is this relatively safe? I don't know much about image encoding, but even if someone went through the trouble of somehow tricking my extension check, the file could never be ran on the server anyways unless there was a security hole elsewhere and the attackers were already into my file system, correct? Here's my extension check:
function validate_ext() { //Function validates that the files extension matches the list of allowed extensions
$extension = $this->get_ext($this->theFile);
$ext_array = $this->extensions;
if (in_array($extension, $ext_array)) { //Check if file's ext is in the list of allowed exts
return true;
echo "ext found";
} else {
$this->error[] = "That file type is not supported. The supported file types are: ".$this->extString;
return false;
}
}
And here's the function that copies the uploaded file to it's final resting place.
if ($_FILES[$this->uploadName]['error'] === UPLOAD_ERR_OK){
$newfile = $this->uploadDir.$this->theFile;
if (!move_uploaded_file($this->tempFile, $newfile)) {
$this->error[] = "The file could not be moved to the new directory. Check permissions and folder paths.";
die($this->error_text());
}else{
$this->error[] = "The file ".$this->originalName." was successfully uploaded.";
if ($this->renameFile == true){
$this->error[] = $this->originalName." was renamed to ".$this->theFile;
}
chmod($newfile , $this->fileperm);
}
}else{
$this->error[] = $this->file_upload_error_message($_FILES[$this->uploadName]['error']);
die($this->error_text());
}
Reading the extension really isnt a good way to check file type. You should read the file mime type... granted that can be faked too, but its more of a hassle to fake.
In Linux world, as long as u gave the file non-executable permission, the file cannot execute. Whether it's .jpeg or it's .bash. That's true the other way around too, .jpeg with an executable permission could be executed too (if the content of that .jpeg file is executable file, not image content).
You can use getimagesize() to check the file itself.

Categories