How can PHP's finfo::buffer be spoofed? - php

When handling uploaded files $_FILES['foo']['type'] is not at all reliable. I've found if you change the extension on OS X the 'type' is changed automatically.
Instead consider:
$fileInfo = new \finfo(FILEINFO_MIME);
$mimeType = $fileInfo->buffer(file_get_contents($_FILES['foo']['tmp_name']));
$mimeType = explode(';', $mimeType);
Now, if I rename a PHP script to .jpg and upload it (on OS X 10.10) $_FILES['foo']['type'] = image/jpeg and $mimeType = text/x-php.
The file type can easily be changed but how can PHP's finfo::buffer be spoofed? What is the difference between what PHP checked for $_FILES['foo']['type'] and finfo(FILEINFO_MIME)?

PHP doesn't check anything in the $_FILES type; when uploading a file, the sending browser is sending meta data of what it thinks the file type is. $_FILES['file']['type'] simply reflects this value uploaded by the browser. Obviously, anyone can spoof this at will.
Finfo uses the magic database, which is simply a collection of identifying characteristics of file types. I.e., all JPEG files have a characteristic header, all ZIP files start a certain way, this file type has these number of leading bytes, that file type has those kinds of trailing bytes etc. etc. This is harder to spoof, if you actually want to produce a valid file of a certain type, but by no means impossible.

$_FILES gets it's type from the Content-Type header of the mime part that contains the file. That part is created by whatever sends the file, usually a browser which will guess the type based on the file extension.
The fileinfo extension, on the other hand, relies on the magic_open library. If I remember correctly, magic_open will check multiple attributes of the file, including file headers to determine the mimetype. Try embedding php in an html file. I believe, since the file header is <!DOCTYPE html> it will determine text/html is the mime type.

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
}

What is the relation between Mime Type and file extension

my question is:
Is there some relation between a file extension and it's mime type? I mean, if i get a file, for instance, .php and change it's extension to .png will also change it's mime type?
Short answer: Yes.
Slightly longer answer: Mime types and file extensions provide hints to how to deal with a file. Whereas file extensions are commonly used for your OS to decide what program to open a file with, Mime types are used by your browser to decide how to present some data (or the server on how to interpret received data). Both are optional but it's a good practice to have an agreement. Changing the mime type a file is served as depends on your webserver. I believe Apache has settings somewhere to map from extensions to mime types. If you have your own back end serving content you can potentially serve content with any arbitrary mime type, for example, in PHP:
<?php
// We'll be outputting a PDF
header('Content-Type: application/pdf');
...
or
<?php
header('Content-Type: application/javascript');
echo "//script code here"
File extensions are hints as to the kind of data the file contains. MIME types are labels for the kind of data in a file. One file extension maps to at most one MIME type. One MIME type maps to zero or more file extensions. A good example is image/jpeg, which maps to both .jpg and .jpeg.
Theory aside, the MIME type a browser gives you is usually reliable, but if you require certainty you must then assume the browser has been compromised.
In such case, on the server using PHP, you can check that a given file matches a given MIME type with the FInfo extension:
$path = '/path/to/your/file.pdf';
$info = finfo_open(FILEINFO_MIME_TYPE);
switch (finfo_file($info, $fpath)) {
case 'application/pdf':
// hooray, this is what you want
// do whatever
break;
default:
throw new RuntimeException('I said give me a PDF!');
}
Or if you want a simple function:
function is_mime_type($path, $mime) {
return (finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path) === $mime);
}
if (is_mime_type('/path/to/file.pdf', 'application/pdf')) {
// hooray
}
Here is a similar answer that documents other approaches to accomplish this goal.
And here's an answer asking about the mapping between file extensions and MIME types.

mp3 upload mimetype being missed by firefox specically

I have a file upload, it works great in everything except for firefox, it keeps saying the mimetype isnt supported. This is my code:
if(isset($_POST[submitfile]))
{
$uploadedsong = $_FILES['soup']['tmp_name'];
$mimetype = $_FILES['soup']['type'];
if($mimetype=="audio/mpeg"||$mimetype=="audio/x-mpeg-3"||$mimetype=="audio/mp3"||$mimetype=="audio/x-mpeg"||$mimetype=="audio/x-mp3"||$mimetype=="audio/mpeg3"||$mimetype=="audio/x-mpeg3"||$mimetype=="audio/mpg"||$mimetype=="audio/x-mpg"||$mimetype=="audio/x-mpegaudio")
{
This is allowing uploads for every browser, EXCEPT firefox! extremely frustrating, i dont know why this is happening. Any ideas?
The mime-type for the file-upload is totally informative and not further explicitly (and specifically) binding of what's-o-ever. Don't rely to it.
Firefox is doing nothing wrong here, it's the wrong expectations you've coded into your script - from the PHP Manual­Docs:
$_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.
So the use of this information is limited, it is not strict.
You should log which mime-type was uploaded, because you can't test against all browser/OS combinations.
Inspecting the file is necessary as well if you want to ensure it follows the convention of a mp3 file. Next to fileinfo­Docs (which is for all files), there is php-reader and Zend_Mimme_Magic and a lot of other mp3 files related libraries.
Try using this to get the mime type
$file_info = new finfo(FILEINFO_MIME);
$mime_type = $file_info->file($file);

How to read header of a file uploaded in 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.

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