How can I check the uploaded files extension in the following code(I already wrote a file type checking)? I want to prevent uploading image files with wrong extension, like *.jpg.exe.
My code:
<?php
class Uploader {
private $fileName;
private $fileData;
private $destination;
public function __construct($key){
$this->fileName = $_FILES[$key]['name'];
$this->fileData = $_FILES[$key]['tmp_name'];
}
public function saveIn($folder){
$this->destination = $folder;
}
public function save(){
$folderWriteAble = is_writable($this->destination);
if($folderWriteAble && (exif_imagetype($this->fileData) == IMAGETYPE_JPEG)){
$name = "$this->destination/$this->fileName";
$success = move_uploaded_file($this->fileData, $name);
} else {
trigger_error("cannot write to $this->destination");
$success = false;
}
return $success;
}
}
If you run on your server(s) linux I would check the file content type with the command file that returns the real mime type of the file. Than you can be sure what that content is (in most cases).
That programm uses that magic bytes. The orginal idea is to check the first view bytes and check if a file contains a known pattern, e.g. "MZ" for windows executables or "‰PNG" for png files. However that file programm does also some more things than only the basic set of the first view bytes.
Depending on the comments, you are concerned about wrong, e.g. double file extensions. I would say don't think about it and just rename that file, in best case with some random name. That could be also helpful if you worry about that somebody just counts up some file numbers to see unpublished images.
I think you already do this on (exif_imagetype($this->fileData) == IMAGETYPE_JPEG), but there's a really good discussion on this here: https://security.stackexchange.com/questions/57856/is-there-a-way-to-check-the-filetype-of-a-file-uploaded-using-php
Use getimagesize which checks the first three bits in the file. Note that $_FILES isn't secure as it reads the extension (which people can change of course), vs getimagesize which reads permission bits.
Usage:
$image = getimagesize($_FILES['image']['tmp_name']);
$filetype = $image['mime'];
Hope this helps
I know this won't necessarily answer your specific question, but a good way to prevent "PHP images" to be "executed" is to have images served from a place that doesn't execute PHP scripts and only serves static images (ie: nginx, if properly configured).
It could even be an external CDN or just a simple directory that doesn't run php.
That being said, you can also try:
1- Make sure file type is (jpg, gif or png)
2- Make sure dimensions are numbers
3- Make sure file size does not exceed allowed size
4- Make sure file is not executable by anyone else (proper chmod settings are important in shared environment).
5- Rename and convert all uploads through imagemagick to jpg (or your desired format)
Use GD library to test if your upload is a jpg and in addition, check if it also returns false for partially uploaded images:
$image = #imagecreatefromjpeg($this->fileData);
if(!$image) { imagedestroy($image); return false; } // file is not a jpg
else { imagedestroy($image); return true; } // file is a jpg
If you can use exec(), you may also invoke the unix file utility for checking the bynary signatures.
// verify that the file is a jpg
$mime = "image/jpeg; charset=binary";
exec("file -bi " . $this->fileData, $out);
if ($out[0] != $mime) {
// file is not a jpg
...
If you have ClamAV installed you can also check for virus with the exec command:
exec("clamscan --stdout " . $this->fileData, $out, $return);
if ($return) {
// file is infected
...
Related
I've looked at a number of the answers to StackOverflow questions on safely uploading images with PHP. I've put this following script together with explainers and wanted to know if this is missing anything. My only/main concern is I can't seem to find much info on stripping out harmful code from the image itself, although this is partly covered in the code.
A couple of SO answers touch on the GD image functionality but they don't really give any good code example cases and because I'm new to php I can't quite seem to wrap my head around how to use this (in terms of creating a new version of the image).
Note: This images in this code go to an '/images' directory, but on the live site they will go into a subdomain called 'images' which is outside the public folder and which will serve static files only (no PHP, Perl etc). The short_open_tag will be turned off in the php.ini file.
The files are selected with a file input type with the name 'profile-image'.
The following code is split into its component parts - the first part is the if/isset statements that check that a submit button called 'submit-profile-image' has been clicked, and the files have been uploaded into memory in the ['tmp_name'] key of the $_FILES superglobal:
if(isset($_POST['submit-profile-image'])) {
$allowed = ['jpeg', 'jpg', 'png'];
if($_FILES['profile-image']['error'] === 0 ) {
// THE DIFFERENT CHECKS IN THE MAIN CODE BELOW GO HERE
} else {
$error[] = "Image failed to load please try again";
}
}
This following code all goes inside the 2nd if statement shown above - I've broken it down to show what it is meant to acheive:
Set variable names of temp upload file and file input name
$profileImageName = $_FILES['profile-image']['name'];
$temp = $_FILES['profile-image']['tmp_name'];
Explode string to split the file name and file extension
$ext = explode('.', $profileImageName);
$ext = strtolower(end($ext));
Completely rename file, and only keep the file extension from the original file:
$file = uniqid('', true) . time() . '.' . $ext;
Sanitize string for extra safety (probably not needed)
$file = filter_var($file, FILTER_SANITIZE_STRING);
$file = strtolower($file);
Check the file extention matches the allowed array of file extensions:
if (!in_array($ext, $allowed)) {
$error[] = "File type must be in the jpg / jpeg or png format";
}
Check MIME type using the getImageSize() function which is more reliable than the 'type' key found in the standard $_FILES superglobal:
$getImageSizeMime = getImageSize($temp);
if(isset($getImageSizeMime['mime']) == false) {
$error[] = "Not a recognised MIME type";
} else {
$getImageSizeMime = $getImageSizeMime['mime'];
}
Make sure the MIME type matches the file extension:
if (in_array($ext, $allowed) != $getImageSizeMime) {
$error[] = "Mime type must be of the jpg / jpeg or png format";
}
Inspect contents of image file itself:
$cleanUpload = file_get_contents($temp) ;
Disallow if file contents contain php or script:
if(preg_match('/(<\?php\s)/', $cleanUpload)) {
$error[] = "Image data cannot contain php script tags";
}
if(preg_match('/script/', $cleanUpload)) {
$error[] = "Image data cannot contain javascript tags";
}
Sanitise file contents of HTML tags
$cleanUpload = filter_var($cleanUpload, FILTER_SANITIZE_STRING);
Move uploaded file if none of the above errors are present
if (!isset($error)) {
if(in_array($ext, $allowed)) {
move_uploaded_file($temp, 'images/' . $file);
}
}
Any input on any security issues missed or any extra checks on the image file itself would be hugely appreciated - particularly on how to duplicated the image to only keep image data using the GD library if possible/necessary? Some of the answers on StackOverflow are very old and seem to feature methods that aren't seen as the most up to date either (which I've avoided in this question).
It would be really good to see if there are any PHP methods for checking image files themselves and removing potentially dangerous code.
Ok here is my code for uploading files
$ext_whitelist = array('pdf','doc','doc','mkv','mp4','mpg','mpeg','avi','flv','wma','ogg');
if(in_array($ext, $ext_whitelist))
{
$uniqid_file = uniqid('', true)."_".$file['name'];
$lokacija = $folder . "/" . $uniqid_file;
$encoded_uniqid_file = base64_encode($uniqid_file);
move_uploaded_file($file['tmp_name'], $lokacija);
$base_url= base_url("forms/fdownload/$encoded_uniqid_file/$path");
$form_data[$key] = "$uniqid_file ";
}
This checks file extension, so easy some could rename file, can someone help me to check file type proper?
Insted of a comment, I'll write a bit more as an answer.
Mimetype checking is a good thing if you want to know the type of the file, but it's not secure if you want to allow/deny the files at upload, because it's very easy to fake the mimetype.
Just try it, you can change it with a proxy or you can create a simple image, then add some php code at the end and rename it to .php. If you only check the mimetype, you can upload this .php file and run it on the server.
If you upload .jpg with php code in it, it's okay, the server won't push it through the php parser. (Except when you change the default configuration. (Apache: AddType, nginx: AddHandler )
There are some "secure" ways to check the uploaded files:
1. Check the extension and compare it to a whitelist.
This is the example in the question, but I'd like to write a complete solution. (A common mistake to check only the first think after the ., because there could be file names like: something.txt.php so always check the last postfix.)
$ext = array_pop(explode(".", $fileName));
$whitelist = array('pdf','doc','doc','mkv','mp4','mpg','mpeg','avi','flv','wma','ogg');
if (in_array($ext, $whitelist) {
//OK the extension is good, handle the upload.
} else {
//Wrong type, add error message.
}
If you use something like this, be careful and never allow extensions like .php and anything wich is in the server config.
2. Rename the file and drop the extension.
This is an another good way, but maybe you want to keep the original file name, the extension and the mimetype. You can store them in a database!
For this solution just take the original filename, add some random data (because if you upload into a single folder and you trie to upload something.jpg 2 time that would be a bad idea), then store this.
For example:
$newName = sha1($fileName.time());
move_uploaded_file($file['tmp_name'], $uploadPath . $newName);
Because the file doesn't have an extension, the server wont try to run it. (But if it's for example an image it'll work in the browsers, because they use the mimetype to determine the type and we didn't changed that.)
You can use
perl-file-mimeinfo
Ex:-
$file_path = '/tmp/temp.jpg';
$mimetype = trim(shell_exec("/usr/bin/mimetype -bi ".escapeshellarg($file_path)));
$info = null;
if(strpos($mimetype, "video/")===0 || strpos($mimetype, 'x-flash-video') > 0){
$info = 'video';
}elseif(strpos($mimetype, "audio/")===0){
$info = 'audio';
}elseif(strpos($mimetype, "image/")===0){
$info = 'image';
}
I am sending image from android apps to server. The problem is image not moving to the correct path, but only at current directory (only in which that php script stored). I tested this codes on local server and webserver, getting same result. Any one can find out whats problems.
Local Server: XAMPP 1.7.7
My PHP Script :
<?php
$base=$_REQUEST['image'];
$Username=$_REQUEST['Username'];
$binary=base64_decode($base);
header('Content-Type: bitmap; charset=utf-8');
$file = fopen($Username.'.png', 'w');
fwrite($file, $binary);
$uploadFilename = '/htdocs/android/ProfileImage/';
$tr =move_uploaded_file($_FILES[$file]['tmp_name'], $uploadFilename);
if($tr)
echo 'true';
else
echo 'false';
echo 'Successfully Uploaded';
?>
Showing Output and Error in Local Server
Strict Standards: Resource ID#3 used as offset, casting to integer (3) in C:\xampp\htdocs\android\uploadSimage.php on line 12
Notice: Undefined offset: 3 in C:\xampp\htdocs\android\uploadSimage.php on line 12
falseSuccessfully Uploaded
Showing Output and Error in Webserver
Notice: Undefined offset: 3 in C:...\uploadSimage.php on line 12
falseSuccessfully Uploaded
move_uploaded_file() expects the second parameter to be a string representing the new path and filename of upload. Currently, you are passing only a path. I also question whether the path is correct. It must be a full path, or a relative path.
You are also using the $_FILES array incorrectly. Are you uploading the image by encoding it in base64 and passing it via the URL's query string? Or are you actually uploading it using a multipart/form-data file upload field?
If you uploaded a file belonging to the upload field called image then you would get access to the file like this:
$origname = $_FILES['image']['name']; // the name from the client device
$temppath = $_FILES['image']['tmp_name']; // the temp location on the PHP server
$error = $_FILES['image']['error']; // > 0 if there was an error
$size = $_FILES['image']['size']; // size of the file
$type = $_FILES['image']['type']; // mime type, cannot be trusted though
You would then move it like this:
// Be careful using the original file name.
// If the user uploads a file with a .php extension, they may be
// able to run PHP code on your server if they can access the upload folder
// You should either generate a random file name or remove the extension
// IF THE DESTINATION FILE EXISTS, IT WILL BE OVERWRITTEN
$newPath = '/home/yoursite/htdocs/uploads/' . $origname;
$moved = move_uploaded_file($_FILES['image']['tmp_name'], $newPath);
if ($moved) {
echo "File was moved successfully.";
} else {
echo "Failed to move file.";
}
EDIT:
If you are in fact uploading the image by encoding it in base64 and sending it over the URL, then you don't need move_uploaded_file at all; in that case you can just write the decoded contents to a file anywhere you like. Keep in mind, the length of the URL may be limited so sending the image in the URL via base64 may not be a good idea.
EDIT 2:
To comment on the questions in your subsequent answer: The php function move_uploaded_file() should only be used when the file you are trying to move was uploaded to PHP using an HTTP POST method upload. It does an internal check to see if the file you are trying to move was uploaded to PHP. If it was not, then it won't move the file. Therefore you shouldn't be using move_uploaded_file() since you confirmed you were uploading the image through the URL.
Since your PHP script's path is C:\xampp\htdocs\android, this means the root path is C:\. The server root is different from your web root or document root which are both relative to your public directory. Any time you are dealing with reading/writing files in PHP, you use the full server path (relative to C:\ or /).
Given the new facts, try some code like this to "upload" the image:
<?php
$base = (isset($_REQUEST['image'])) ? $_REQUEST['image'] : '';
$Username = (isset($_REQUEST['Username'])) ? trim($_REQUEST['Username']) : '';
$binary = #base64_decode($base);
if (empty($Username)) {
die('no username specified');
}
if (!$binary) {
// data was not in base64 or resulted in an empty string
die('invalid image uploaded');
}
$basePath = 'C:\\xampp\\htdocs\\android\\ProfileImage\\';
$imagePath = $basePath . $Username . '.png';
$file = #fopen($imagePath, 'w+');
if (!$file) {
die('failed to open ' . $imagePath . ' for writing');
}
fwrite($file, $binary);
fclose($file);
echo 'Successfully Uploaded';
Make sure to take the necessary precautions so I can't upload an image for another user.
per to this document http://php.net/manual/en/function.move-uploaded-file.php another reason for this problem is invalid File name if your file Name in
move_uploaded_file ( string $filename , string $destination ) be invalid
this function return false
I accessed my server using file zilla and give write Group permissions to the target folder and then it worked.
If you're using XAMPP:
sudo chmod 777 -R /opt/lampp/htdocs/
I'm currently writing an upload class for uploading images. I do extension checks to verify that the uploaded images are of the supported types, and the photos are always chmod(0664) when the uploaded file is copied to it's resting place. Is this relatively safe? I don't know much about image encoding, but even if someone went through the trouble of somehow tricking my extension check, the file could never be ran on the server anyways unless there was a security hole elsewhere and the attackers were already into my file system, correct? Here's my extension check:
function validate_ext() { //Function validates that the files extension matches the list of allowed extensions
$extension = $this->get_ext($this->theFile);
$ext_array = $this->extensions;
if (in_array($extension, $ext_array)) { //Check if file's ext is in the list of allowed exts
return true;
echo "ext found";
} else {
$this->error[] = "That file type is not supported. The supported file types are: ".$this->extString;
return false;
}
}
And here's the function that copies the uploaded file to it's final resting place.
if ($_FILES[$this->uploadName]['error'] === UPLOAD_ERR_OK){
$newfile = $this->uploadDir.$this->theFile;
if (!move_uploaded_file($this->tempFile, $newfile)) {
$this->error[] = "The file could not be moved to the new directory. Check permissions and folder paths.";
die($this->error_text());
}else{
$this->error[] = "The file ".$this->originalName." was successfully uploaded.";
if ($this->renameFile == true){
$this->error[] = $this->originalName." was renamed to ".$this->theFile;
}
chmod($newfile , $this->fileperm);
}
}else{
$this->error[] = $this->file_upload_error_message($_FILES[$this->uploadName]['error']);
die($this->error_text());
}
Reading the extension really isnt a good way to check file type. You should read the file mime type... granted that can be faked too, but its more of a hassle to fake.
In Linux world, as long as u gave the file non-executable permission, the file cannot execute. Whether it's .jpeg or it's .bash. That's true the other way around too, .jpeg with an executable permission could be executed too (if the content of that .jpeg file is executable file, not image content).
You can use getimagesize() to check the file itself.
I have a .doc file and I renamed named it to give it a .jpg extension. When I process the renamed file with my function it accepts the file as having a .jpg although the file is not really a JPEG. What's the best way to find the actual file type? Here is my current code:
function getExtension($str) {
$i = strrpos($str,".");
if (!$i) { return ""; }
$l = strlen($str) - $i;
$ext = substr($str,$i+1,$l);
return $ext;
}
What's the best way to check the file's type without depending on the extension?
You can use PHP's Fileinfo to detect file types based on their contents; however, it is only available in PHP 5.3.0 or if you have access to install PECL extensions.
It returns a file's mime-type, such as text/html, image/gif, or application/vnd.ms-excel which should be more accurate (accounts for contents possibly having multiple common extensions, such as: jpg, jpe, jpeg).
This is the one I use and it has never failed me.
function getExtension($str)
{
$i = explode('.', $str);
return strtolower(end($i));
}
I don't know if you can check the actual original file extension if someone has renamed it, you would have to have the spec of every file type you were checking for I'm not sure how easy it is to tell. But you could verify something was an image like this:
function isImage($file)
{
if(#getimagesize($file) == false)
return false;
else
return true;
}
Summing up
if(isImage($file) && getExtention($file) == "jpg")
{
//Process, it's a valid image
}
Firstly, with respect to file extension, on't reinvent the wheel. Use PHP's pathinfo() function:
$extension = pathinfo($filename, PATHINFO_EXTENSION);
Secondly, you're quite right: the file extension tells you nothing about it's contents.
Third, you should where possible determine the contents of the file yourself rather than relying on either the file extension or the MIME type (from file uploads). Both are arbitrary and simply specified by the client.
Images are fairly easy because you can load the file with the GD library. It'll fail if its not an image and you can interrogate it for size, etc.
Word documents are harder. If you're running on Windows, you can make a call to the system to load the file and see if it does load. I'm not aware of any native PHP library that can do this.
If you just want to get the file extension, you could use pathinfo:
function getExtension($str)
{
$info = pathinfo($str)
return $info['extension'];
}
However, it sounds like you want to check if the file you're given is a valid image. To do that, you can do something similar with the getimagesize() function:
function getMimeType($str)
{
$info = #getimagesize($str)
if (!$info)
{
// Uh oh, this wasn't a valid image -- do some error handling
return NULL;
}
return $info['mime'];
}
Using this, you can check the MIME type of the image ("image/jpeg", etc.), and validate if the given file is really the type of image you think it is.
You can use explode(".", $sFileName) and take the last item of the array to do this, but that one fails when you have something like filename.tar.gz, when you want the last two array items, or filename.class.php, when, after you have implemented the last-two-array-items-thing, you just want the last one.
The best way of doing this is, in my opinion, pathinfo($sFileName). This returns an array with the keys dirname, basename, extension (if any), and filename.
If you want the file type, though, you'll have to use something like PHP 5.3's built-in Fileinfo to get the file's MIME-type. (getimagesize() returns the MIME-type too, but this only works on images!)
function file_extension($filename) {
$path_info = pathinfo($filename);
return $path_info['extension'];
}