How to read header of a file uploaded in PHP? - php

Can we read the header information of a file in PHP to determine the type of file uploaded?.
I don't want to rely on $_FILES['control_name_from_client']['type']. As we know that this property determines the file type by reading the extension of the file uploaded.
What if the user renames, say test.jpg -> test.xls. In that case, $_FILES['control_name_from_client']['type'] will show the type as application/vnd.ms-excel instead of image/jpeg. It is but natural this can create problems if a code has to be executed which reads the XLS file to fetch data for some processing.
Any suggestions please?

Try finfo_file(). You have to call it passing the filepath. Example:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['control_name_from_client']['tmp_name']);
finfo_close($finfo);
You need the Fileinfo extension. As PHP manual says:
The functions in this module try to guess the content type and encoding of a file by looking for certain magic byte sequences at specific positions within the file. While this is not a bullet proof approach the heuristics used do a very good job.

as far as I'm aware, there is no such function in PHP, but if you have access to the CLI (and are running Linux), you could use the "file" command through system().

There used to be the mime_magic extension in older versions of PHP, but that's deprecated now in favour of finfo_file, which does use file signatures to test the filetype, not purely the extension.

Dunno what "header" you are talking about, but from the security point of view the only thing you really have to pay attention to is a filename extension.
Just because your web-server would judge your file by it.
To test if uploaded file being valid data for some particular application, you have to use this application-specific routine, there are no universal tool in PHP.
you can use imagemagick for images, getid3 for the mp3 files, fffmpeg for the movies and so on.
But of course whole file have to be employed, checking just "header" doesn't guarantee that entire file is valid.

Related

JSON file generated by PHP has application/octet-stream mime type

I have a script that generates a JSON file from data.
I have a second script that read files from a directory to take only JSON ones and insert them in DB.
The problem is that the second script detects "application/octet-stream" MIME type from my generated files instead of application/json
I don't want to allow application/octet-stream MIME type as it can be pretty anything (for security reason: that second script load all json file in the directory (not only the generated ones)).
Is there then anyway to "set" a MIME type for a file?
The code that generate the file :
if($r_handle = fopen($s_file_name, 'w+')){
fwrite($r_handle, json_encode($o_datas, JSON_HEX_QUOT | JSON_HEX_TAG));
fclose($r_handle);
return;
}
The code that read JSON files :
$o_finfo = finfo_open(FILEINFO_MIME_TYPE);
$a_mimes =& get_mimes();
if(is_dir($s_dir) && $r_handle = opendir($s_dir)){
while($s_file = readdir($r_handle)){
$s_file_path = $s_dir.$s_file;
$s_mime = finfo_file($o_finfo, $s_file_path);
if(!in_array($s_file, array('.', '..')) && in_array($s_mime, $a_mimes['json'])){
// Some code
}
}
}
The fileinfo extension (as similar tools like the file Unix command) basically searches for signatures defined in a database (called "magic"). If I'm not wrong, PHP's magic database is currently compiled into the extension binary file so you can't peek at it but you'll probably have a similar database in your system. I have Apache's at C:\Apache24\conf.magic and this is the entry for JPEG:
# JPEG images
0 beshort 0xffd8 image/jpeg
Anything that starts with 0xffd8 is a picture. Done!
I'm not particularly familiar with the format but it doesn't seem to even look for JSON. And, as you may already be guessing, the overall utility is by no means a security feature. It's just a helper tool to figure out what a file may contain. It's very handy if e.g. you've recovered files with no extension from a damaged disk.
MIME types are cool. You set application/json and everybody knows it's JSON. Straightforward and simple, isn't it. There're only two caveats:
File systems (many of them actually invented before MIME types) store many file attributes (name, last modification date, permissions, sometimes even icons...) but not MIME types. (Sure, there's probably some academic file system that does, but it's not the case of FAT32, NTFS, ext4...). It doesn't normally add valuable information, it's yet another token to keep updated and it's particularly non-portable (copy your files to a thumb drive and they're gone).
It's still not a security feature. If I can forge the file contents, what prevents me from forging the MIME type?
So, what can you do? The best alternative is: nothing at all.
Just parse the file as JSON and detect whether it failed. You need to do it anyway and it tells you everything you need to do. JSON is just plain text data. Maybe add some checks to prevent very large files (again, you should be doing it anyway in your file upload) and add a $depth check but that's all.
if (json_decode($s_file_path, true, 32)!==null || json_last_error()!==JSON_ERROR_NONE) {
// Valid JSON
}

Is it safe to upload files after validating by mime_content_type() and imagecreatefromjpg()?

I read a lot of articles (including this) talking about secure file upload in PHP and I think this is the best way to make sure the file uploaded is safe.
First, I use mime_content_type() to check its file's type.
If it is JPG, JPEG or PNG, I'm gonna clone it with imagecreatefromjpg(), if success, save the new file and destroy the original.
I'm not sure this is safe enough or I need to do something else.
I appreciate any help.
TL;DR: mime_content_type checks real file content to detect type
I did some testing for mime_content_type
When I created text file and named it "spoofily" spoof.jpg...the detected mime type was still text/plain
When I ran it on non existing file, it throws
PHP Warning: mime_content_type(not.exist):
failed to open stream: No such file or directory
This means mime_content_type checks file content for mime type.
Is it totally safe though? It is safe enough to detect the true mime type, that's for sure ;)
However you should also check for things like size or sanitizing filename (when reused), for more security details, there is a nice answer on Security Stack: Risks of a PHP image upload form
note that in the PHP manual, this is under "Fileinfo Functions" menu => it kind of implies it uses file-read mechanisms, though it is true manual entry doesn't say this explicitly...
"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

PHP secure file upload

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 :)

Best Mime Type Method

Whats the best way to determine the mime type or file type , stopping anything malicious getting through and making sure a bug doesn't get in your system.
In my example I need a way of screening so just .mp3 are uploaded to the site. Now I know there is mime_content_type but that gives odd results depending on how the file was made and what browser you use, seeing as it gets its data from the browser, at least that's how I understand it.
this is my code for identifying using mime type.
if(mime_content_type_new($_FILES["userfile"]) == 'audio/mpeg' ) { do stuff }
then there is using unix command line and interpreting that
$fileinfo = exec("file -b 'song.mp3'"); echo $filinfo;
this outputs something like this.
Audio file with ID3 version 2.3.0, contains: MPEG ADTS, layer III, v1,
192 kbps, 44.1 kHz, Stereo
so we can sort through and check this t match to our file type.
$fileinfo = exec("file -b 'song.mp3'");
$filewewant = "MPEG";
$mpeg = stripos($fileinfo, $filewewant);
$filewewant = "layer III";
$mpeg3 = stripos($fileinfo, $filewewant);
if ($mpeg !== False & $mpeg3 !== False)
{ echo "success"; };
this way seems to work better, regardless of named extension (eg is it renamed it .png) but requires the file to be saved first then sorted through,and doesn't work on windows.
I've also been pointed at http://pear.php.net/package/MIME_Type
Does anyone else have a better way of doing it ? what is the correct way to identify what files are being uploaded to your server ?
MIME types are (should be) obtained by looking at the file's MIME header, a piece of data at the beginning of the file that indicates the MIME.
This is exactly what mime_content_type_new and your UNIX command are doing, so no issue there.
Not sure what you mean by a "better" way, you're doing it correctly.
If you are getting different MIME type results because of a browser issue, you should probably create an array of acceptable values and check it with the in_array() method.
I wouldn't recommend leaving MIME type checks like that in the hands of client-side code, especially when security is a big issue. The client has access to the code so it is much easier to fool.
You could, however, do a check on both the client side and the server side. This will save you bandwidth from bad uploads, but still keep the system secure from malicious users.
Here's a nice tutorial on Javascript's FILE API and processing images with Javascript.
http://www.html5rocks.com/en/tutorials/file/dndfiles/
Cheers.
This it maybe not a proof solution (just new / current browsers), but the new javascript FILE API allows to read the MIME-TYPE without uploading the file.
For any server-side validation you have to upload the file -> and you should validate them.

Limiting file upload 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.

Categories