I'm implementing a user-based image uploading tool for my website. The system should allow any users to upload JPEG and PNG files only. I'm, of course, worried about security and so I'm wondering how the many smarter people than myself feel about the following checks for allowing uploads:
1) First white list the allowable file extensions in PHP to allow only PNG, png, jpg, JPG and JPEG. Retrieve the user's file's extension via a function such as:
return end(explode(".", $filename));
This should help disallow the user from uploading something malicious like .png.php. If this passes, move to step 2.
2) Run the php function getimageize() on the TMP file. Via something like:
getimagesize($_FILES['userfile']['tmp_name']);
If this does not return false, proceed.
3) Ensure a .htaccess file is placed within the uploads directory so that any files within this directory cannot parse PHP files:
php_admin_value engine Off
4) Rename the user's file to something pre-determined. I.E.
$filename = 'some_pre_determined_unique_value' . $the_file_extension;
This will also help prevent SQL injection as the filename will be the only user-determined variable in any queries used.
If I perform the above, how vulnerable for attack am I still? Before accepting a file I should hopefully have 1) only allowed jpgs and pngs, 2) Verified that PHP says it's a valid image, 3) disabled the directory the images are in from executing .php files and 4) renamed the users file to something unique.
Thanks,
Regarding file names, random names are definitely a good idea and take away a lot of headaches.
If you want to make totally sure the content is clean, consider using GD or ImageMagick to copy the incoming image 1:1 into a new, empty one.
That will slightly diminish image quality because content gets compressed twice, but it will remove any EXIF information present in the original image. Users are often not even aware how much info gets put into the Metadata section of JPG files! Camera info, position, times, software used... It's good policy for sites that host images to remove that info for the user.
Also, copying the image will probably get rid of most exploits that use faulty image data to cause overflows in the viewer software, and inject malicious code. Such manipulated images will probably simply turn out unreadable for GD.
Regarding your number 2), don't just check for FALSE. getimagesize will also return the mime type of the image. This is by far a more secure way to check proper image type than looking at the mime type the client supplies:
$info = getimagesize($_FILES['userfile']['tmp_name']);
if ($info === FALSE) {
die("Couldn't read image");
}
if (($info[2] !== IMAGETYPE_PNG) && ($info[2] !== IMAGETYPE_JPEG)) {
die("Not a JPEG or PNG");
}
All the checks seem good, number 3 in particular. If performance is not an issue, or you are doing this in the background, you could try accessing the image using GD and seeing if it is indeed an image and not just a bunch of crap that someone is trying to fill your server with.
Concerning No. 2, I read on php.net (documentation of the function getimagesize() ):
Do not use getimagesize() to check that a given file is a valid image. Use a purpose-built solution such as the Fileinfo extension instead.
Related
There's an upload form on my website. I'm actually not really including or excluding file types.
Instead I'm using this:
$fileUploadName = $target_dir.md5(uniqid($target_file.rand(),true)).".".$imageFileType;
That will keep the file type but change the file name to some random cryptic like 790cd5a974bf570ff6a303c3dc5be90f.
This way a hacker cannot upload a hack.php file and the open it with www.example.com/uploaded_files/hack.php because it has changed to e.g. 790cd5a974bf570ff6a303c3dc5be90f.php. In my view it's completely safe this way. Am I right that it's safe this way?
I think only a self-executing-file could be a problem. Do self-executing-files even exist?
You should also eighter check the mime type of the file uploaded and the extension (although that can easily be faked on upload).
If you expect only images, you could also check for image width and length parameters by executing a script like this:
$size = getimagesize($target_file);
If this does not return proper values, it's no image file.
You might want to inform yourself about dangerous graphics like Gifar too.
Put uploaded files in there own folder like /etc/web/uploads/<Random> (../uploads) while the website root is at /etc/web/public/ or if you can use .htaccess create one in the uploads folder and put Deny from all
Also uniqid and rand does not generate cryptographically secure values I would also check that the uploaded file is an image anyway Link to someones isimage function http://php.net/manual/en/function.uniqid.php http://php.net/manual/en/function.rand.php
Use file_get_contents to get the users image
I know there is a big amount of questions about this but I cannot get one that involves all I want to be aware of.
What I want to do is to allow the users of my webpage to upload images with a form. And I want to do this process secure, or at least as much secure I can.
I do not know too much about security in terms of deep inside of it, but I am aware of about all the consequences that a insecure webpage can produce. And I cannot be quiet thinking that my webpage is insecure or that anyone is not going to enter into my webpage because it does not have enough visits(I am realist).
At this point, I know that all the checks about security have to be done on server side instead of client side (or in both).
I know that a file can be fooled as an image and run malicious code so I searched about methods to avoid this. This is what I could find to check before store the image on the server:
From $_FILES:
$_FILES['file']['name']: To check that the file that I have uploaded have a name. To know that the file exists.
$_FILES['file']['error']: To check if the image have an error.
$_FILES['file']['size']: To check that the size of the image is bigger than 0.
$_FILES['file']['type']: To check that the type of the file is an image but it is not recommended because PHP does not check it.
General functions:
Check magic numbers to verify the image type.
exif_imagetype(): To check the type of an image.
getimagesize(): To check if it returns a 0 which means that the file is not an image.
imagecreatefromstring(): To create a new image giving a string. If it cannot be created, then is not an image.
imagepng: To create a PNG image to remove all meta-data (using imagecreatetruecolor() and imagecopy()).
But the problem I have is that I do not know if I should use all of these methods or just avoid or add some of them (because some of them seems redundant).
And my questions are:
Should I use all of them?
Have I to add another one method to be more secure?
Could be the order in which I filter the file critic? I mean, is it better to use one filter before another and viceversa? If so, what should be the order and why?
Note: I am not searching about personal opinion. I tried to gather all info I could, but I cannot be sure if it is ok or not talking about security terms. If you can put examples of something that it is forgotten it would be great.
Thanks in advance!
To answer your questions:
You don't need to use all of those methods, and which ones you use are going to be based on personal opinion. Meaning to say, there is more than one perfectly secure way to do it so don't be suprised if you get multiple different answers.
See examples below for additional checks you might have left out
Yes, the order definitely matters.
Depending on your application, the logic for any secure upload should flow something like this:
Is the user logged in? (optional)
// make sure user is logged in
if (!$user->loggedIn()) {
// redirect
}
Does the user have permission? (optional)
// make sure user has permission
if (!$user->isAllowed()) {
// redirect
}
Was the form submitted?
// make sure form was submitted
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
Is the form input valid?
// validate CSRF token
// ...
// make sure there were no form errors
if ($_FILES['file']['error'] == UPLOAD_ERR_OK) {
// make sure the file size is good
if ($_FILES['file']['size'] <= MAX_FILE_UPLOAD) {
// make sure we have a valid image type
$type = exif_imagetype($_FILES['file']['tmp_name']);
if ($type !== false) {
// make sure we check the type against a whitelist
if (in_array(ltrim(image_type_to_extension($type), '.'), array('jpeg', 'jpg', 'png'))) {
Even after validating, never trust user input
// give the file a unique name
$hash = hash_file('sha1', $_FILES['file']['tmp_name']);
$ext = image_type_to_extension($type);
$fname = $hash . $ext;
Save the file (or optionally recreate it with a library to strip out meta-data) but NEVER in a publicly accessible directory
$upload_path = '/path/to/private/folder';
move_uploaded_file($_FILES['file']['tmp_name'], "$upload_path/$fname");
The steps above are perfectly secure and more than reasonable, of course there is always a risk that some other part of your application or server might be vulnerable.
I would do the following with an apparent image upload:
1) Use is_uploaded_file() to ensure you've not been fooled into working on something else entirely
if(!is_uploaded_file($yourfile))
return false;
2) Check the mimetype with exif_imagetype() and block anything you don't want
$allowed_images = array(IMAGETYPE_BMP, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
$uType = exif_imagetype($yourfile);
if(!in_array($uType, $allowed_images))
{
unlink($yourfile);
return false;
}
3) Use Imagick to remake the image and remove all comments and metadata:
$image = new Imagick($yourfile);
$image->resizeImage($image->getImageWidth(), $image->getImageHeight(), Imagick::FILTER_CATROM, 1);
$image->stripImage(); // remove all comments and similar metadata
4) Write the replacement image to the filesystem and erase the original file:
$image->writeImage("/path/to/new/image");
unlink($yourfile);
5) Upload this image to S3.
// your S3 code here
6) Make a note of the image's S3 URL in the database or wherever.
// your database code here
7) Erase the replacement image.
unlink("/path/to/new/image");
If you get enough responses, you might have a good answer! :-)
Operating System
Make sure you have a dedicated volume for the files. Or, at minimum, have quota set on the directory. Make sure you have enough inodes and such, if on Linux/Unix. A bunch of small files can be just as deadly as a few gigantic files. Have a dedicated uploads directory. Set where the temp files should go in your php.ini. Make sure your file permission are safe (chmod), too. Use Linux ACLs, if necessary, to fine tune permissions. Test, test, test.
PHP
Incorporate the knowledge found here into your uploaded file handling algorithm PHP Manual: POST method uploads. Take the MAX_FILE_SIZE bit with a grain of salt.
Make sure you know what your max up load file size is. Set it accordingly. There may be other file related settings. Be sure to lock those in before getting around to the $_FILES superglobal.
Do not work with the uploaded files directly, and do not use the name attribute at all to give the file a real file name. Use, is_uploaded_file() and move_uploaded_file() appropriately.
Use tmp_name appropriately.
Be wary of null bytes on file names! Yes, you still need to filter and validate any string that represents user input (especially if you intend on using it any way).
First things first, check for the presence of a file.
Second, check the size in bytes.
If anything in #5 or #6 fail, the validation process should end. For a robust routine, incorporate the idea that at sometime you may want to upload multiple files at one time (PHP Manual: Uploading Multiple Files). In that case, the $_FILES superglobal may not look like you would expect. See the link above for more details.
GD
You've got the general idea about using these functions to open the submitted file (without using the user submitted name, that is). Just come up with a logical series of progressive steps. I don't have those steps, but if meta-data can be a problem, that would seem high on the list of GD stuff to try early (after basic file presence and size stuff). I could be wrong though.
I am solving some web hacking challenges from natas overthewire. In level 12, I have to upload an image on the server and then the server gives me the precise link where it got the image uploaded. I exploit this by writing a simple php script echoing the contents of the password. This works because I can upload it as a .php file and the server also stores it in that format. In level 13, similar challenge is presented but here the server check if it is an image file by calling the function exif_imagetype(). I just modify an existing image file in notepad and insert my script at the end.
My question is if the server simply checked the extension of the file and rejected if the file was not .jpg extension, it would be better because in that case even if I uploaded a script it would never get executed by the server as it would be interpreted as an image file and php server would never parse the file. So why use such a function as exif_imagetype() ?
Validating a true image is always better than examining the file extension. Unless the extension checker is particularly complex, it will likely look for the last . in the filename, and assess what follows.
For example, this might be an example of a rudimentary extension checker:
$extension = end( explode('.', $_FILES['my_file']['name'] );
// or:
$extension = strrchr($_FILES['file']['name'], '.');
That's all well and good, and indeed performs perfectly well for file names such as myfile.jpg, malicious.php, etc.
However, this can be easily spoofed by an unscrupulous user with a variety of hacks. The most difficult to circumvent is the infamous NULL byte injection:
shell.php%0delete0.jpg
You can learn more about null-byte injection here.
I have a PHP website where people can fill out help-tickets. It allows them to upload screenshots for their ticket. I allow gif, psd, bmp, jpg, png, tif to be uploaded. Upon receiving the upload, the PHP script ignores the file extension. It identifies the filetype using only the MIME information, which for these filetypes is always stored within the first 12 bytes of the file.
Someone uploaded several GIFs, which when viewed with a browser, the browser said it was invalid, and my virus scanner alerted me that it was a injection (or something like that). See below for a zip file containing these GIFs.
I don't think only checking header info is adequate. I have heard that an image can be completely valid, but also contain exploit code.
So I have two basic questions:
Does anyone know how they did injected bad stuff into a GIF (while still keeping a valid GIF MIME type)? If I know this, maybe I can check for it at upload time.
How can I prevent someone from uploading files like this?
I am on shared hosting so I can't install a server-side virus
scanner.
Submitting the info to a online virus scanning website
might be too slow.
Is there any way to check myself using a PHP class that checks for these things?
Will resize the image using GD fail if it's not valid? Or would the exploit still slip through and be in the resized image? If it fails, that would be ideal because then I could use resizing as a technique to see if they are valid.
Update: Everyone, thanks for replying so far. I am attempting to look on the server for the GIFs that were uploaded. I will update this post if I find them.
Update 2: I located the GIFs for anyone interested. I put them in a zip file encrypted with password "123". It is located here (be careful there are multiple "Download" buttons on this hosting site -- some of them are for ads) http://www.filedropper.com/badgifs. The one called 5060.gif is flagged by my antivirus as a trojan (TR/Graftor.Q.2). I should note that these files were upload prior to me implementing the MIME check of the first 12 bytes. So now, I am safe for these particular ones. But I'd still like to know how to detect an exploit hiding behind a correct MIME type.
Important clarification: I'm only concerned about the risk to the PC who downloads these files to look at them. The files are not a risk to my server. They won't be executed. They are stored using a clean name (a hex hash output) with extension of ".enc" and I save them to disk in an encrypted state using an fwrite filter:
// Generate random key to encrypt this file.
$AsciiKey = '';
for($i = 0; $i < 20; $i++)
$AsciiKey .= chr(mt_rand(1, 255));
// The proper key size for the encryption mode we're using is 256-bits (32-bytes).
// That's what "mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)" says.
// So we'll hash our key using SHA-256 and pass TRUE to the 2nd parameter, so we
// get raw binary output. That will be the perfect length for the key.
$BinKey = hash('SHA256', '~~'.TIME_NOW.'~~'.$AsciiKey.'~~', true);
// Create Initialization Vector with block size of 128 bits (AES compliant) and CBC mode
$InitVec = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
$Args = array('iv' => $InitVec, 'key' => $BinKey, 'mode' => 'cbc');
// Save encoded file in uploads_tmp directory.
$hDest = fopen(UPLOADS_DIR_TMP.'/'.$Hash.'.enc', 'w');
stream_filter_append($hDest, 'mcrypt.rijndael-128', STREAM_FILTER_WRITE, $Args);
fwrite($hDest, $Data);
fclose($hDest);
As for the first question, you'll never really know if you're not able to retrieve any logs or the images in question, because there are many things these exploit may have targeted and depending on what's the target the way the exploit was put into the file can be completely different.
Edit: W32/Graftor is a generic name for programs that appear to have trojan-like characteristics.
After opening the file 5060.gif in a hex editor, I noticed the program is actually a renamed windows program. Although it's not a browser exploit and thus harmless unless it's actually opened and executed, you'll have to make sure it isn't served with the MIME type defined by the uploader because a user may still be tricked into opening the program; see the answer to the second question.
As for the second question: to prevent any exploit code from being run or a user, you'll have to make sure all files are stored with a safe extension in the filename so they are served with the correct MIME type. For example, you can use this regular expression to check the file name:
if(!preg_match ( '/\\.(gif|p(sd|ng)|tiff?|jpg)$/' , $fileName)){
header("415 Unsupported Media Type");
die("File type not allowed.");
}
Also make sure you're serving the files with the correct Content Type; make sure you don't use the content type specified with the uploaded file when serving the file to the user. If you rely on the Content-Type specified by the uploader, the file may be served as text/html or anything similar and will be parsed by the users' browser as such.
Please note that this only protects against malicious files exploiting vulnerabilities in the users' browser, the image parser excluded.
If you're trying to prevent exploits against the server you'll have to make sure that you won't let the PHP parser execute the contents of the image and that the image library you are using to process the image does not have any known vulnerabilities.
Also note that this code does not defend you against images that contain an exploit for the image parser used by the users browser; to defend against this, you can check if getimagesize() evaluates to true as suggested by Jeroen.
Note that using getimagesize() alone isn't sufficient if you don't check file names and make sure files are served with the correct Content-Type header, because completely valid images can have HTML / PHP code embedded inside comments.
You can use the getimagesize() function for this. If the image is invalid it will simply return false.
if (getimagesize($filename)) {
// valid image
} else {
// not a valid image
}
It's worth noting that this isn't 100% safe either, but it's the best you can do as far as I know.
Read more about this here.
I dont know much about image formats, but recreating the images and then storing the result, I feel has a good chance of eliminating unnecessary tricky stuff. Especially if you strip all the meta data like comments and all the other types of optional embedded fields that some image formats support.
You can try phpMussel on any php script that accepts uploads. The file will be scanned using ClamAV signatures, plus some internal heuristic signatures that look for this type of intrusion specifically.
1) You're never going to know exactly what the problem was if you deleted the .gif and your A/V didn't write a log.
Q: Is the .gif in question still on the server?
Q: Have you checked your A/V logs?
2) There are many different possible exploits, which may or may not have anything directly to do with the .gif file format. Here is one example:
http://www.phpclasses.org/blog/post/67-PHP-security-exploit-with-GIF-images.html
3) To mitigate the risk in this example, you should:
a) Only upload files (any files) to a secure directory on the server
b) Only serve files with specific suffixes (.gif, .png, etc)
c) Be extremely paranoid about anything that's uploaded to your site (especially if you then allow other people to download it from your site!)
On very usefull tip to prevent problems with injected PHP came from my host's system admin: I have a site where people can uploaded their own content. I wanted to make sure the directory where uploaded images are served from doesn't run any PHP. That way someone could even post a picture named "test.php" and it would still NEVER be parsed by PHP if it was in the upload directory. The solution was simple: In the folder the uploaded content is served from put the following .htacess:
RewriteEngine On
RewriteRule \.$ - [NC]
php_flag engine off
This will switch off the PHP engine for the folder, thus stopping any attempt to launch any PHP to exploit server side vulnerabilities.
Late response, but may be useful for somebody.
You may try such approach:
//saves filtered $image to specified $path
function save($image,$path,$mime) {
switch($mime) {
case "image/jpeg" : return imagejpeg(imagecreatefromjpeg($image),$path);
case "image/gif" : return imagegif(imagecreatefromgif($image),$path);
case "image/png" : return imagepng(imagecreatefrompng($image),$path);
}
return false;
};
if ((($_FILES["uploadedfile"]["type"] == "image/gif")
|| ($_FILES["uploadedfile"]["type"] == "image/jpeg")
|| ($_FILES["uploadedfile"]["type"] == "image/pjpeg"))
&& ($_FILES["uploadedfile"]["size"]/1024<100))
When I test the code above, I found that a user can bypass the file type check by simply modifying the extension name, how to get the real file extension name?
Also, when a user uploads a very large file, how to immediately reject the upload on the server side?
you cannot really accurately get the file type from the mimetype since it's an user input and could be easily forget. What you can do is to use the file command on *nix to make sure it is a real jpeg or gif files. On the same fashion you can try to load it with GD (image extension) or Image magic.
an example of file output
olivier#olivier-laptop:~/trust/public/images$ file verisign_sample.gif
verisign_sample.gif: GIF image data, version 89a, 100 x 60
using the backtice operator you would be able to get the result and parse it
$line = `file $filepath`
For your second question it depends on the browser,in the RFC the browser don't have to supply the content size when making the request, so you cannot stop the upload if the file is very large. It will upload until it reaches your PHP upload limit(upload_max_filesize parameter of php.ini) and PHP would kill the request.
Indeed, a user can bypass the file check by modifying the file extension. You can't rely on anything that is contained under either "name" or "type" in $_FILES, as these can both be user-supplied, therefore meaning that can definitely not be considered trustworthy.
As RageZ pointed out, you can use the file command for determining file type, or try loading the image with GD or something similar. You can also look at the file header yourself: there is an interesting article on doing this here
If you want to provide only image uploads, you check for the file type by using getimagesize() and then image_type_to_mime_type().