Validate uploaded file type.

Share your own tutorials here.

Validate uploaded file type.

Postby wide_load » Sat Feb 20, 2010 12:57 am

Okay I'm going to explain how to make sure that a file that is being uploaded is in fact an image and not a PHP file with a faked mime type.

If you only want to allow images there is a very simple way that it can be done using the mime_content_type() function, which determines the type from the file its self rather than relying on the information sent from the browser.

Here is an example of the functions usage
  1. echo mime_content_type('image.png');
this would output
  1. image/png


all image mime types will start with image/

so for image validation all that needs to be done is.
  1. <?php
  2.  
  3. $type = mime_content_type($_FILES['name']['tmp_name']);
  4. $type = explode('/', $type);
  5.  
  6. if (strtolower($type[0]) === 'image'){
  7.     // its an image.
  8. }else{
  9.     // its not an image.
  10. }
  11.  
  12. ?>
I found that in some cases the mime type can be returned in upper case, which is the reason for the strtolower() being used.

Additional to this it is advisable to check the extension of the file, which can be obtained using the following method
  1. $ext = strtolower(end(explode('.', $_FILES['name']['name'])));

again the strtolower is just to make sure that if the file has a uppercase extension and is valid, it will still pass.

Then we need a list of allowed extensions to compare with.
  1. $allowed = array('png', 'jpg', 'gif', 'jpeg', 'svg');


then we need to check the extension we got from the file name to make sure its in the list of allowed types, php has a function for this called in_array(). so putting it all together we have.
  1. <?php
  2.  
  3. $allowed = array('png', 'jpg', 'gif', 'jpeg', 'svg');
  4. $ext = strtolower(end(explode('.', $_FILES['name']['name'])));
  5.  
  6. if (in_array($ext, $allowed) === true){
  7.     // it has a allowed extension.
  8. }else{
  9.     // it dosnt have a valid extension.
  10. }
  11.  
  12. ?>


now we should really combine these two checks into one nice clean statement, so using the && opperator on the if statement we end up with...
  1. <?php
  2.  
  3. $allowed = array('png', 'jpg', 'gif', 'jpeg', 'svg');
  4.  
  5. $ext = strtolower(end(explode('.', $_FILES['name']['name'])));
  6. $type = mime_content_type($_FILES['name']['tmp_name']);
  7. $type = explode('/', $type);
  8.  
  9. if (strtolower($type[0]) === 'image' && in_array($ext, $allowed) === true){
  10.     // upload can continue.
  11. }else{
  12.     // abort abort abort...
  13. }
  14.  
  15. ?>


So now we are 100% sure we have a safe file ? no !!
There is no way to be completely sure its possible there is a way I don't know about to trick this, its possible there is a way nobody knows about yet.

so saving the file with its original name is not a good idea,because if someone does manage to upload a .php file and it is saved as a .png then there is no way that they will be able to get it to execute.

Happily the correct extension can easily be determined by looking at the mime type, so once we have decided that the image is safe to upload we need to work out what to call it and where to save it.

Lets start with a list of extensions that will go with each of the allowed mime types.
  1. <?php
  2.  
  3. $extension['image/png'] = 'png';
  4. $extension['image/jpeg'] = 'jpg';
  5. $extension['image/gif'] = 'gif';
  6. $extension['image/svg+xml'] = 'svg';
  7.  
  8. ?>


Now as we know the mime type of the file we can work out what its extension should be...
  1. $saveas = $extension[$type];
as we have now confirmed the file is one of these types it should not get no extension so there is no need to check to see if the extension is valid here. Saving the file with no extension would not be an advantage to the attacker anyway.

It is also best to generate a random name for the file (if possible for your system) this can be done is a number of ways, alex's random password tutorial is a good method (ill be using a one line method for the purpose of this tutorial, its up to you), however we also need to make sure that the file does not exist, before we decide on a name for it. This is done using a while loop...

First we need to decide on a name, then we need to generate a new random name while that name exists in the folder, hopefully this will become more clear with an example...
  1. $name = md5(rand(0, 9999999));
  2. while (file_exists('/path/where/file/is/saved/'.$name.'.'.$saveas)){
  3.     $name = md5(rand(0, 9999999));
  4. }
Hopefully you can see why the loop will only continue until it finds a file name which is not taken. After this we save this path in a variable for ease.
  1. $savepath = '/path/where/file/is/saved/'.$name.'.'.$saveas;
So putting this part together and moving the file we get...
  1. <?php
  2.  
  3. $extension['image/png'] = 'png';
  4. $extension['image/jpeg'] = 'jpg';
  5. $extension['image/gif'] = 'gif';
  6. $extension['image/svg+xml'] = 'svg';
  7.  
  8. $saveas = $extension[$type];
  9.  
  10. $name = md5(rand(0, 9999999));
  11. while (file_exists('/path/where/file/is/saved/'.$name.'.'.$saveas)){
  12.     $name = md5(rand(0, 9999999));
  13. }
  14. $savepath = '/path/where/file/is/saved/'.$name.'.'.$saveas;
  15.  
  16. move_uploaded_file($_FILES['name']['tmp_name'], $savepath);
  17.  
  18. ?>


The final step is to decide what happens when the file is not valid (my advice is to tell the user nothing and just kill the script) and combine these two parts...

The final script looks like this...
  1. <?php
  2.  
  3. $allowed = array('png', 'jpg', 'gif', 'jpeg', 'svg');
  4.  
  5. $ext = strtolower(end(explode('.', $_FILES['name']['name'])));
  6. $type = mime_content_type($_FILES['name']['tmp_name']);
  7. $type = explode('/', $type);
  8.  
  9. if (strtolower($type[0]) === 'image' && in_array($ext, $allowed) === true){
  10.     $extension['image/png'] = 'png';
  11.     $extension['image/jpeg'] = 'jpg';
  12.     $extension['image/gif'] = 'gif';
  13.     $extension['image/svg+xml'] = 'svg';
  14.  
  15.     $saveas = $extension[$type];
  16.  
  17.     $name = md5(rand(0, 9999999));
  18.     while (file_exists('/path/where/file/is/saved/'.$name.'.'.$saveas)){
  19.         $name = md5(rand(0, 9999999));
  20.     }
  21.     $savepath = '/path/where/file/is/saved/'.$name.'.'.$saveas;
  22.  
  23.     move_uploaded_file($_FILES['name']['tmp_name'], $savepath);
  24. }else{
  25.     die();
  26. }
  27.  
  28. ?>
Last edited by wide_load on Wed Mar 17, 2010 12:24 pm, edited 4 times in total.
Image
User avatar
wide_load
Top Contributor
 
Posts: 5617
Joined: Thu Aug 13, 2009 1:04 pm
Online: 13d 22h 25s
Karma: 45

Re: Validate uploaded file type.

Advertisment

Advertisment
 

Re: Validate uploaded file type.

Postby extra » Sat Feb 20, 2010 9:20 am

Wow... Just yesterday i saw the mime_content_type function on php.net
Thanks!
Karma++; me if I helped! :roll:
GameZ4Free::playGames();

I was Hyper.. Now I'm extra.
User avatar
extra
Top Contributor
 
Posts: 1020
Joined: Fri Nov 06, 2009 7:08 pm
Online: 7d 57m 23s
Karma: 18

Re: Validate uploaded file type.

Postby Cags » Sat Feb 20, 2010 10:07 am

One of the safest ways to deal with images is to use one of the image functions (whether GD or ImageMagik) to simply open the image as an image and resize it to it's current size before saving it again. If it is not a valid image this is highly unlikely to work. For example use imagecreatefromjpeg to get the image, getimagesize to find it's dimensions and finally imagecopyresized to create the actual image again.
"I don't suffer from insanity, I enjoy every minute of it."
- Pete
CodeCanyon - Cheap, High Quality, Ready Made Scripts.
User avatar
Cags
Moderator
 
Posts: 1863
Joined: Fri May 22, 2009 9:35 am
Location: Purgatory
Online: 3d 1h 51m 14s
Karma: 9

Re: Validate uploaded file type.

Postby wide_load » Sat Feb 20, 2010 3:14 pm

Cags wrote:One of the safest ways to deal with images is to use one of the image functions (whether GD or ImageMagik) to simply open the image as an image and resize it to it's current size before saving it again. If it is not a valid image this is highly unlikely to work. For example use imagecreatefromjpeg to get the image, getimagesize to find it's dimensions and finally imagecopyresized to create the actual image again.


yes, resampling is the best way however if you don't actually want to resize the image then its quite a bloated way.

I also think that this method alone would fail to prevent against php code in the comment of a gif file
Image
User avatar
wide_load
Top Contributor
 
Posts: 5617
Joined: Thu Aug 13, 2009 1:04 pm
Online: 13d 22h 25s
Karma: 45

Re: Validate uploaded file type.

Postby Cags » Sun Feb 21, 2010 12:35 am

As far as I know including php code in the comment of a gif would be harmless. But I haven't tested that theory.
"I don't suffer from insanity, I enjoy every minute of it."
- Pete
CodeCanyon - Cheap, High Quality, Ready Made Scripts.
User avatar
Cags
Moderator
 
Posts: 1863
Joined: Fri May 22, 2009 9:35 am
Location: Purgatory
Online: 3d 1h 51m 14s
Karma: 9

Re: Validate uploaded file type.

Postby wide_load » Sun Feb 21, 2010 1:20 am

Cags wrote:As far as I know including php code in the comment of a gif would be harmless. But I haven't tested that theory.


the point is the the gd functions don't make sure that the file is an image before they try to use it.

So it is possible to make a .php file that looks like a .gif.

Thats how the imageshack.us hack happened i think...

by the way if anyone works out a way to beat this method of validation, I would like to know, I use it on a few of my sites.
Image
User avatar
wide_load
Top Contributor
 
Posts: 5617
Joined: Thu Aug 13, 2009 1:04 pm
Online: 13d 22h 25s
Karma: 45

Re: Validate uploaded file type.

Postby libeco » Mon Feb 22, 2010 9:34 pm

I just looked at this tutorial and found this bit a little bit odd:
  1. <?php
  2.  
  3. $type = mime_content_type('image.png');
  4. $type = explode('/',$type);
  5.  
  6. if (strtolower($_FILES['name']['tmp_name']) === 'image'){
  7.         // its an image.
  8. }else{
  9.         // its not an image.
  10. }
  11.  
  12. ?>


Shouldn't this be:
  1. <?php
  2.  
  3. $type = mime_content_type('image.png');
  4. $type = explode('/',$type);
  5.  
  6. if (strtolower($type) === 'image'){
  7.         // its an image.
  8. }else{
  9.         // its not an image.
  10. }
  11.  
  12. ?>

:?:
Common PHP/MySQL errors | How to display code on the forum
"It always seems impossible until its done." - Nelson Mandela
User avatar
libeco
Moderator
 
Posts: 2466
Joined: Fri Apr 24, 2009 9:03 pm
Location: Netherlands
Online: 5d 9h 32m 31s
Karma: 25

Re: Validate uploaded file type.

Postby wide_load » Mon Feb 22, 2010 9:42 pm

libeco wrote:I just looked at this tutorial and found this bit a little bit odd:
  1. <?php
  2.  
  3. $type = mime_content_type('image.png');
  4. $type = explode('/',$type);
  5.  
  6. if (strtolower($_FILES['name']['tmp_name']) === 'image'){
  7.         // its an image.
  8. }else{
  9.         // its not an image.
  10. }
  11.  
  12. ?>


Shouldn't this be:
  1. <?php
  2.  
  3. $type = mime_content_type('image.png');
  4. $type = explode('/',$type);
  5.  
  6. if (strtolower($type) === 'image'){
  7.         // its an image.
  8. }else{
  9.         // its not an image.
  10. }
  11.  
  12. ?>

:?:


I think it should ill have a look :)

You were half right

its fixed now,

thats what happeens when you try to write guides that early in the morning.
Image
User avatar
wide_load
Top Contributor
 
Posts: 5617
Joined: Thu Aug 13, 2009 1:04 pm
Online: 13d 22h 25s
Karma: 45

Re: Validate uploaded file type.

Postby libeco » Mon Feb 22, 2010 10:12 pm

Although I don't have an immediate use for it now, thanks for sharing, I'm sure it will come in handy in the future!
Common PHP/MySQL errors | How to display code on the forum
"It always seems impossible until its done." - Nelson Mandela
User avatar
libeco
Moderator
 
Posts: 2466
Joined: Fri Apr 24, 2009 9:03 pm
Location: Netherlands
Online: 5d 9h 32m 31s
Karma: 25

Re: Validate uploaded file type.

Postby attasz » Sat Mar 20, 2010 5:45 pm

Nice job wie_load!Thank you for sharing.
By the way,how is it possible to make a php file which looks like a gif?
User avatar
attasz
 
Posts: 368
Joined: Thu Aug 20, 2009 10:51 am
Online: 1d 22h 5m 2s
Karma: 6

Next

Return to Tutorials

Who's online?

Users browsing this forum: No registered users and 1 guest