Checking for mime type in php is pretty easy but as far as I know mime can be spoofed. The attacker can upload a php script with for example jpeg mime type. One thing that comes to mind is to check the file extension of the uploaded file and make sure it matches the mime type. All of this is assuming the upload directory is browser accessible.
Question: Are there any other techniques for preventing "bad files" from getting in with mime type spoofing?
Short answer: No.
Longer answer:
Comparing the extension and making sure that it matches the MIME type doesn't really prevent anything. As was said in the comments, it's even easier to modify a file extension. MIME type and extension are only to be meant as hints, there's no inherent security in them.
Ensuring that incoming files do no harm is very dependent on what your purpose for them is going to be. In your case I understood that you are expecting images. So what you could do is perform some sanity checks first: scan the first couple of bytes to see if the files contain the relevant image header signatures - all relevant image formats have these.
The "signature headers" help you to decide what kind of image format a file tries to impersonate. In a next step you could check if the rest of the contents are compliant with the underlying image format. This would guarantee you that the file is really an image file of that specific format.
But even then, the file could be carefully crafted in a way that when you display the image, a popular library used to display that image (e.g. libpng etc.) would run into a buffer overflow that the attacker found in that library.
Unfortuantely there's no way to actively prevent this besides not allowing any input from the client side at all.
Caution - this answer is now obsolete
The documentation for getimagesize explicitly states "Do not use getimagesize() to check that a given file is a valid image.
In case of Images
Check the extension with a list of allowed ones (ex. ".jpg", ".jpeg", ".png")
Check the uploaded file itself by running getimagesize on the file, it will return FALSE if it's not an image.
Other types of upload
Check the allowed extensions (ex. ".pdf")
Check that the mime type of the file corresponds to the extension
Sample code:
function getRealMimeType($filename) {
$finfo = new finfo(FILEINFO_MIME, "/usr/share/misc/magic");
if (!$finfo) {
echo "Opening fileinfo database failed";
return "";
}
return $finfo->file($filename);
}
See finfo_file documentation.
"mime_content_type" and "exif_imagetype" should not be used for security purposes because both of them allow spoofed files!
More details from link below:
https://straighttips.blogspot.com/2021/01/php-upload-spoofed-files.html
File extension check in order to block dangerous file extensions such as ".php" is the best way to go if files are going to be uploaded somewhere in the "public_html" folder!
Antivirus scan may be a nice alternative because some spoofed files are detected by antivirus!
Check the extension.
<?php
$okFiles = array('jpg', 'png', 'gif');
$pathInfo = pathinfo($filename);
if(in_array($pathInfo['extension'], $okFiles)) {
//Upload
}
else {
//Error
}
?>
You can also - like you said - check if the extension match the MIME type, but it's much more easy to just check the extension.
Btw why do you care about the MIME type?
Related
Checking for mime type in php is pretty easy but as far as I know mime can be spoofed. The attacker can upload a php script with for example jpeg mime type. One thing that comes to mind is to check the file extension of the uploaded file and make sure it matches the mime type. All of this is assuming the upload directory is browser accessible.
Question: Are there any other techniques for preventing "bad files" from getting in with mime type spoofing?
Short answer: No.
Longer answer:
Comparing the extension and making sure that it matches the MIME type doesn't really prevent anything. As was said in the comments, it's even easier to modify a file extension. MIME type and extension are only to be meant as hints, there's no inherent security in them.
Ensuring that incoming files do no harm is very dependent on what your purpose for them is going to be. In your case I understood that you are expecting images. So what you could do is perform some sanity checks first: scan the first couple of bytes to see if the files contain the relevant image header signatures - all relevant image formats have these.
The "signature headers" help you to decide what kind of image format a file tries to impersonate. In a next step you could check if the rest of the contents are compliant with the underlying image format. This would guarantee you that the file is really an image file of that specific format.
But even then, the file could be carefully crafted in a way that when you display the image, a popular library used to display that image (e.g. libpng etc.) would run into a buffer overflow that the attacker found in that library.
Unfortuantely there's no way to actively prevent this besides not allowing any input from the client side at all.
Caution - this answer is now obsolete
The documentation for getimagesize explicitly states "Do not use getimagesize() to check that a given file is a valid image.
In case of Images
Check the extension with a list of allowed ones (ex. ".jpg", ".jpeg", ".png")
Check the uploaded file itself by running getimagesize on the file, it will return FALSE if it's not an image.
Other types of upload
Check the allowed extensions (ex. ".pdf")
Check that the mime type of the file corresponds to the extension
Sample code:
function getRealMimeType($filename) {
$finfo = new finfo(FILEINFO_MIME, "/usr/share/misc/magic");
if (!$finfo) {
echo "Opening fileinfo database failed";
return "";
}
return $finfo->file($filename);
}
See finfo_file documentation.
"mime_content_type" and "exif_imagetype" should not be used for security purposes because both of them allow spoofed files!
More details from link below:
https://straighttips.blogspot.com/2021/01/php-upload-spoofed-files.html
File extension check in order to block dangerous file extensions such as ".php" is the best way to go if files are going to be uploaded somewhere in the "public_html" folder!
Antivirus scan may be a nice alternative because some spoofed files are detected by antivirus!
Check the extension.
<?php
$okFiles = array('jpg', 'png', 'gif');
$pathInfo = pathinfo($filename);
if(in_array($pathInfo['extension'], $okFiles)) {
//Upload
}
else {
//Error
}
?>
You can also - like you said - check if the extension match the MIME type, but it's much more easy to just check the extension.
Btw why do you care about the MIME type?
I have an upload form which allowed most of file types to be uploaded.
Here they are:
Image: jpg/jpeg/png/gif ...
Video: mp4/avi/wmv ...
another files: doc/pdf/rar/zip/mp3/...
For image file, I know I can use PHP function getimagesize() or something else to make sure it's the real image. But how about the other files such as Video, and documentation ? Is it a real file without faking the extension ?
How to do that?
Thank you! I need your help.
every file has it's own type, it called mime type , so u can check the mime type , do some things like that :
if (mime_content_type($FILES['file']['tmp'])== "image/png"))
{
// do upload
}else{
die('file type not supported');}
u can put all the mime type into an array the check the type with in_array function
u can find all the mime type here : http://www.freeformatter.com/mime-types-list.html
Any client-side check (even the browser mime-type detection) is doomed to fail because user has access to it. You'd better let the upload begin and complete, then perform a serious server side check. You simply discard the file if that is not what you expected to be.
On top of the server-side check you can of course implement the client-side check just for a neater user experience
The only way to fully secure a file upload is to attempt parsing the uploaded file with PHP or some other extension/tool that expects a specific valid file type. In other words:
Images: use GD functions to parse the file, they'll return false if it isn't a valid image.
Videos: could probably validate using ffmpeg on the command line and check the output or use the ID3 extension as suggested here: https://stackoverflow.com/a/134893 (credit to Zathrus Writer's comment on the question linking to this question)
Documents: Attempt loading the file with PHPExcel/Word/PowerPoint although I'm not sure that these would support just any format of those documents as it works on OpenXML.
From looking at the getID3 extension this might be the best bet as it seems to parse a wide variety of content types.
In any case, what you essentially need is for PHP or some other 3rd party library/extension to determine that the binary data of a file corresponds with its MIME content type.
Hope this helps :)
I am creating file upload script and I'm looking for the best techniques and practices to validate uploaded files.
Allowed extensions are:
$allowed_extensions = array('gif','jpg','png','swf','doc','docx','pdf','zip','rar','rtf','psd');
Here's the list of what I'm doing.
Checking file extension
$path_info = pathinfo($filename);
if( !in_array($path_info['extension'], $allowed_extensions) ) {
die('File #'.$i.': Incorrent file extension.');
}
Checking file mime type
$allowed_mimes = array('image/jpeg','image/png','image/gif','text/richtext','multipart/x-zip','application/x-shockwave-flash','application/msword','application/pdf','application/x-rar-compressed','image/vnd.adobe.photoshop');
if( !in_array(finfo_file($finfo, $file), $allowed_mimes) ) {
die('File #'.$i.': Incorrent mime type.');
}
Checking file size.
What should I do to make sure uploaded files are valid files? I noticed strange thing. I changed .jpg file extension to .zip and... it was uploaded. I thought it will have incorrect MIME type but after that I noticed I'm not checking for a specific type but if a specific MIME type exist in array. I'll fix it later, that presents no problems for me (of course if you got any good solution/idea, do not hesitate to share it, please).
I know what to do with images (try to resize, rotate, crop, etc.), but have no idea how to validate other extensions.
Now's time for my questions.
Do you know good techniques to validate such files? Maybe I should unpack archives for .zip/.rar files, but what about documents (doc, pdf)?
Will rotating, resizing work for .psd files?
Basically I thought that .psd file has following mime: application/octet-stream but when
I tried to upload .psd file it showed me (image/vnd.adobe.photoshop). I'm a bit confused about this. Do files always have the same MIME type?
Also, I cannot force code block to work. Does anyone have a guess as to why?
Lots of file formats have a pretty standard set of starting bytes to indicate the format. If you do a binary read for the first several bytes and test them against the start bytes of known formats it should be a fairly reliable way to confirm the file type matches the extension.
For example, JPEG's start bytes are 0xFF, 0xD8; so something like:
$fp = fopen("filename.jpg", "rb");
$startbytes = fread($fp, 8);
$chunked = str_split($startbytes,1);
if ($chunked[0] == 0xFF && $chunked[1] == 0xD8){
$exts[] = "jpg";
$exts[] = "jpeg";
}
then check against the exts.
could work.
If you want to validate images, a good thing to do is use getimagesize(), and see if it returns a valid set of sizes - or errors out if its an invalid image file. Or use a similar function for whatever files you are trying to support.
The key is that the file name means absolutely nothing. The file extensions (.jpg, etc), the mime types... are for humans.
The only way you can guarantee that a file is of the correct type is to open it and evaluate it byte by byte. That is, obviously, a pretty daunting task if you want to try to validate a large number of file types. At the simplest level, you'd look at the first few bytes of the file to ensure that they match what is expected of a file of that type.
Simple question. Is there a way to only allow txt files upon uploading? I've looked around and all I find is text/php, which allows PHP.
$uploaded_type=="text/php
When you upload a file with PHP its stored in the $_FILES array. Within this there is a key called "type" which has the mime type of the file EG $_FILES['file']['type']
So to check it is a txt file you do
if($_FILES['file']['type'] == 'text/plain'){
//Do stuff with it.
}
It's explained very well here. Also, don't rely on file extentions it's very unreliable.
Simply put: there's no way. Browsers don't consistently support type limiters on file upload fields (AFAIK that was planned or even is integrated into the HTML standard, but barely implemented at best). Both the file extension and mime-type information are user supplied and hence can't be trusted.
You can really only try to parse the file and see if it validates to whatever format you expect, that's the only reliable way. What you need to be careful with are buffer overflows and the like caused by maliciously malformed files. If all you want are text files, that's probably not such a big deal though.
You could check the mime type of the uploading file. In codeIgniter, this code is used in the upload library:
$this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $_FILES[$field]['type']);
The variable $this->file_type then used to check the upload configuration, to see if the uploaded file is in allowed type or not. You can see the complete code in the CodeIgniter upload library file.
You need to check the file extension of the uploaded file.
There is Pear HttpUpload, it supports this.
I am re-building my photo-uploading section on my site, I am trying to learn as much as I can so I can do it securely but also with best performance. MY site has aound 15-20 photos uploaded per minute usually
So is this method reliable for gettting the photo's file type, like jpg, gif, png?
$fileType = $_FILES['image']['type'];
File extensions lie, or can at least. It is definitely best not to rely on/trust user submitted data. Also, as the PHP manual notes:
$_FILES['userfile']['type'] -
The mime type of the file, if the browser provided this information. An example would be "image/gif". This mime type is however not checked on the PHP side and therefore don't take its value for granted.
This is not reliable.
Here's a better way:
PHP's getimagesize() function returns a numerically indexed array of data about the image, and index 2 is one of the IMAGETYPE_XXX constants (a full list of which is available here) indicating the type of the image. These can then be used in a number of GD family functions, the two relevant ones being image_type_to_extension() and image_type_to_mime_type().
So, you could easily do something along these lines:
$imageData = getimagesize($_FILES['userfile']['tmp_name']);
// $imageData[2] will contain the value of one of the constants
$mimeType = image_type_to_mime_type($imageData[2]);
$extension = image_type_to_extension($imageData[2]);
Although, iif you have the exif extension available, the [exif_imagetype()][5] function will return the exact same result as index 2 of getimagesize() but is much faster.
I've used the GD methods as my primary example, because they are more commonly present across PHP installs. But the Imagick extension also offers similar functionallity, and you could also verify the mime type with the fileinfo extension (included since 5.3, btw).
exif_imagetype() is the safest, fastest and most reliable way to check if a given file is a valid image file, it also works with remote URLs.
The return value is the same value that getimagesize() returns in index 2
but exif_imagetype() is much faster.
$type = #exif_imagetype($_FILES['image']['tmp_name']);
if (($type >= 1) && ($type <= 3))
{
echo 'Valid image (GIF, JPG or PNG).';
}
else
{
unlink($_FILES['image']['tmp_name']); // delete it
}
First try getimagesize or exif_imagetype(),
if it doesn't return ['mime'], try finfo_file (if extension_loaded('fileinfo')),
if still no results, try mime_content_type (if function_exists('mime_content_type')).
If you don't want mime type, but just file extension, use pathinfo($path, PATHINFO_EXTENSION).
As the doc says,
The mime type of the file, if the browser provided this information. [...] This mime type is however not checked on the PHP side and therefore don't take its value for granted.
In other word, you shouldn't trust it because I could upload pretty much anything I want and make it look like a jpeg to you.
To make sure an uploaded file is actually an image, I've personnaly used Imagick::identifyImage in the past with excellent results. GD probably has the same kind of function available.
Or you could get the mime type server side using the fileinfo extension, more specifically finfo_file()