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.
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.
I'm following a tutorial for uploading image files using php on udemy. I can choose an image and upload it to a folder without any problems.
When I click on the image after it has been uploaded to the folder, windows photo viewer says: "photo.png It appears that you don't have permission to view this file. Check the permissions and try again".
When I checked permissions it said "You must have read permissions to view the properties of this file".
I used the chmod function set to 0755, which allows the owner to read and write, and lets everyone else read it. I tried changing the chmod codes but it didn't help.
I'm thinking it has something to do with my server permissions, but can't find any solution on google. My images are uploaded to Abyss Web Server.
Here is the code:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
function upload_file() {
//setup
$tmp_name = $_FILES['file']['tmp_name'];
$target_dir = 'uploads/';
$target_file = $target_dir . basename($_FILES['file']['name']);
$max_file_size = 5000000; //5mb
$allowed_file_types = array('application/pdf; charset=binary');
$allowed_image_types = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
//check if image type is allowed
$image_check = getimagesize($tmp_name);
if(! in_array($image_check[2], $allowed_image_types)) {
//if not allowed image check if allowed file type
exec('file -bi' . $tmp_name, $file_check);
if(! in_array($file_check[0], $allowed_file_types)) {
return 'This file type is not allowed';
}
}
//check if file already exists
if(file_exists($target_file)) {
return 'Sorry that file already exists';
}
//check file size
if(file_exists($target_file)) {
return 'Sorry this file is too big';
}
//store the file
if(move_uploaded_file($tmp_name, $target_file)) {
chmod($target_file, 0644);
return 'Your file was uploaded';
}
else {
return 'There was a problem storing your file. Try again?';
}
}
if(! empty($_FILES)) {
echo upload_file();
}
?>
<form action="" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="file">
<input type="submit" Value="upload">
</form>
Since loading the file using a custom made HTML page specifically for testing motives does show the image correctly, then it's most likely a hotlink protection issue. (This was found out after a few comments to the question).
In cPanel, for example, there is a tool to manage this feature and it revolves around the usage of a file called .htaccess. This file is used for a lot of things in the web development world.
Some people don't like their copyrighted images to be accessed, so one way to avoid inexperienced people (let's say, "people in userland") from doing that is to enable this protection. This works for any given file extension that you set it up to.
One way to address this issue is to go to cPanel and disable (or modify accordingly) the Hotlink Protection feature. Another way, is to find the .htaccess file that is causing the issue, which requires understanding the way it works and the syntax it uses.
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
...
Good morning guys. I have created two php files that successfully upload files to my server. One file is the visual part for my website called upload.php and the other is the upload file part called upload_file.php.
the code to upload my files is
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
This works perfectly however it lets me upload any file type. So since I want to only allow zipped folders i tried this if statement.
if($type=="application/zip" ){
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";}
else{
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
where $type=$_FILES['file']['type'];
But now it doesnt let me upload any files not even zipped ones. So what do i need to put in my if statement to only allow zipped folders to be upload? And if your really good guys what do i need to put in my if statement to allow only zipped foleders with the naming convention of USERINITIAL.DATE.TIME or USERINITIAL/DATE/TIME or can this not be done?
You can use this solution
$fileName = strtolower($fileName);
$allowedExts = array('zip');
$extension = explode(".", $fileName);
$extension = end($extension);
if(in_array($extension, $allowedExts))
{
//move file to custom folder
}
IMPORTANT *
Never not use from mime time for identification file type,because it bypass with tamper data.
Best way:
Move your all uploaded file into out of public_html,and Always rename file name,when you want upload this.
And so,save uploaded file name into database,and read file from one php file,for example:
read.php?id=5
in your read.php file,you should get id number and search on database for it,then,return file name from db and download or read this file with read.php file.
Due to some discussion on this thread, heres a little bonus info.
Generally speaking, it's really, really hard to determine if a file is actually the kind of file we want. You can check the mime type, which can be modified by the client. You can check the file extension, which can also be modified by the client- Vice versa.
You can even check the first few lines of a file, which typically contains some sort of header, explaining what kind of file we'r handling. But still then, the file might be modified by some evil genius making the executing program buffer overflow or exploits some library used, to open/view/taste/throw the file.
Lets check both file extension and mime.
First, the extension.
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$isZipExtension = ( $extension == "zip" ? true : false );
On your "$_FILES" array, you have a index called "Type".
Now, you would like to restrict the upload to only accept Zip files.
Theres a few types defining a potential zip file. So lets create an array.
$zipTypes = array('application/zip', 'application/x-zip-compressed',
'multipart/x-zip', 'application/x-compressed');
Now, lets check if the type uploaded, is part of the array.
$isZipFile = in_array( $_FILES['file']["type"], $zipTypes );
If, the file is in the array, do your upload process.
if( $isZipFile && $isZipExtension) {
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";
} else {
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
All together
$zipTypes = array('application/zip', 'application/x-zip-compressed',
'multipart/x-zip', 'application/x-compressed');
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$isZipExtension = ( $extension == "zip" ? true : false );
$isZipFile = in_array( $_FILES['file']["type"], $zipTypes );
if( $isZipFile && $isZipExtension) {
move_uploaded_file($_FILES['file']['tmp_name'],"./medetrax_backup/{$_FILES['file']['name']}");
echo "<div id='mes'> File upload complete</div>";
} else {
echo "<div id='mes'>This file type cannot be uploaded. Only zip folders with the naming convention INITIAL.DATE.TIME are accepted</div>";
}
Hope it helps.
Ironically, you should never use the type key to check the type of file being uploaded. That's because the value of that key is set by the client and can be trivially spoofed.
What you should be doing instead is checking the file extension (which at least makes sure that no well-configured program on your server will treat the upload in an unexpected manner):
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$allowed = ['zip'];
if (in_array($ext, $allowed)) {
// process the file
}
how can i make sure that no php/html files are uploaded to my server? this is my code i have so far but it isn't working.
<?php
$target = "upload/";
$target = $target . basename( $_FILES['uploaded']['name']) ;
$ok=1;
//This is our size condition
if ($uploaded_size > 35000)
{
echo "Your file is too large.<br>";
$ok=0;
}
//This is our limit file type condition
if ($uploaded_type =="text/php")
{
echo "No PHP files<br>";
$ok=0;
}
//Here we check that $ok was not set to 0 by an error
if ($ok==0)
{
Echo "Sorry your file was not uploaded";
}
//If everything is ok we try to upload it
else
{
if(move_uploaded_file($_FILES['uploaded']['tmp_name'], $target))
{
echo "The file ". basename( $_FILES['uploadedfile']['name']). " has been uploaded and will be revied by moderators. You will recieve points based on the review.";
}
else
{
echo "Sorry, there was a problem uploading your file.";
}
}
?>
Your code uses variables which are not set, for example, $uploaded_size which will be NULL unless you do something like...
$uploaded_size = $_FILES['uploaded']['size'];
Also, checking the MIME is not too great at telling you whether the file has PHP or not. It just means it has the php extension (that is if you are inspecting type in $_FILES).
For security, move uploads outside of the docroot, rename and drop any extension (to prevent Apache trying to run any malicious file). The original filename and type can be stored safely in a database, with a reference to the (perhaps hashed) new name.
You may also want to ensure if you are streaming the content later to always echo the content using readfile() and not something like include (which will run your PHP code, even if embedded in an image with image/gif MIME, which can be told it is a GIF if it includes the GIF header).
Check out http://www.php.net/manual/en/function.exif-imagetype.php - this checks for certain magic numbers that all JPG's have at the beginning. Also, as others have pointed out, you're using undefined variables... check out the PHP tutorial for file uploading ( which also documents the contents of $_FILE).
http://www.php.net/manual/en/features.file-upload.post-method.php