0

hi want my user to upload images to my server. There is a lot of security risks i am aware of, like:

  1. client side validation is not a good idea.
  2. PHP code can be embedded into various other data types.(like embeded in a image file)
  3. by using $_FILES["file"]["type"] for detecting file type is another risk.
  4. user can use null byte.

so how can i secure my uploads so that user can fake the extensions or embeded the image with php code. or prevent user from bypassing security checks.

here is the code i found on the internet while searching but cant get it to work.

<?php

$userfile = $_POST['user_profile_image'];

//Upload Files


  // Configuration
      $maxsize = 2097152; // Maximum filesize in BYTES (currently 2MB).
      $upload_path = $uploaddir; // The place the files will be uploaded to (currently a 'files' directory).

   $filename = $_FILES['userfile']['name']; // Get the name of the file (including file extension).
   $ext = substr($filename, strpos($filename,'.'), strlen($filename)-1); // Get the extension from the filename.

   // Check if the filetype is allowed, if not DIE and inform the user.
   if(!in_array($ext,$allowed_filetypes)){
      echo 'Opps! Image Format not allowed!';
    exit;
 }

list($width,$height,$type,$attr) = getimagesize($_FILES['userfile']['tmp_name']);
$mime = image_type_to_mime_type($type);

if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png")) {

die("<BR><BR>Error3: Upload file type un-recognized. Only .JPG or .PNG images allowed.");
}

   // Now check the filesize, if it is too large then DIE and inform the user.
   if(filesize($_FILES['photo1']['tmp_name']) > $max_filesize){
    echo 'Opps! Image is to big!';
    exit;
 }
   // Check if we can upload to the specified path, if not DIE and inform the user.
   if(!is_writable($upload_path)){
      die('You cannot upload to the specified directory, please CHMOD it to 777.');
 }

 //This line assigns a random number to a variable. You could also use a timestamp here if you prefer. 
$ran = rand () ;

//This takes the random number (or timestamp) you generated and adds a . on the end, so it is ready of the file extension to be appended.
$ran2 = $ran;

//This assigns the subdirectory you want to save into... make sure it exists!
$target = "./uploads/";
//This combines the directory, the random file name, and the extension
$target = $target . $ran2.$ext; 

if(move_uploaded_file($_FILES['userfile']['tmp_name'], $target)) 
{
echo "<p>Your image was successfully uploaded!</p>
<p><strong>Forums</strong><br />
  <input name=\"textfield\" type=\"text\" id=\"textfield\" size=\"75\"   value=\"[URL=http://".$siteurl."][IMG]http://".$siteurl.$uploaddir.$ran2.$ext."[/img][/URL]\"/>
</p>
<p><strong>Forums (2)</strong><br />
  <input name=\"textfield2\" type=\"text\" id=\"textfield2\" size=\"75\"  value=\"[url=http://".$siteurl."][img=".$siteurl.$uploaddir.$ran2.$ext."][/url]\"/>
</p>
<p><strong>Direct Link</strong><br />
  <input name=\"textfield4\" type=\"text\" id=\"textfield4\" value=\"".$siteurl.$uploaddir.$ran2.$ext."\" size=\"75\" /><p>
";

}
else
{
    echo 'Opps! Looks like we have a problem....<br><br>';
    echo '<b>Error: <FONT COLOR="#FF3300"><u>There was a weird error!</u></b></font>';
    exit;
exit;
}
?>

i am getting following errors.

Undefined index: user_profile_image in E:\wamp\www\user\test.php on line 3
Undefined variable: uploaddir in E:\wamp\www\user\test.php on line 10
Undefined index: userfile in E:\wamp\www\user\test.php on line 12
Undefined variable: allowed_filetypes in E:\wamp\www\user\test.php on line 16
in_array() expects parameter 2 to be array, null given in E:\wamp\www\user\test.php on line 16

my html code (form)

<form name="upload_image" method="post" action="test.php" enctype="multipart/form-data">
<input type="file" name="user_profile_image" style="position:relative; width:90%;">
<button class="btn btn-info" type="submit" style="float:right;z-index:11; border-radius:0px; border:1px solid #fff; margin:15px 15px 15px 5px;"><font face="arial" size="2px">Upload</font></button>
</form>

what is wrong with this code??
If you have any sollution share it.

2
Contributors
17
Replies
95
Views
3 Years
Discussion Span
Last Post by cereal
Featured Replies
  • 1
    cereal 1,419   3 Years Ago

    ## Regarding your script ## There are some variables that are not set, for example at line 10: $upload_path = $uploaddir; Where is the value of `$uploaddir`? At line 3, instead: $userfile = $_POST['user_profile_image']; You're expecting a value from POST, but in the form this input field is `file` type: … Read More

1

Regarding your script

There are some variables that are not set, for example at line 10:

$upload_path = $uploaddir;

Where is the value of $uploaddir?

At line 3, instead:

$userfile = $_POST['user_profile_image'];

You're expecting a value from POST, but in the form this input field is file type:

<input type="file" name="user_profile_image">

So, it will be handled by the $_FILES array, check these examples:

At line 12:

$filename = $_FILES['userfile']['name'];

But the form will send user_profile_image.

At line 16, you have another missing variable $allowed_filetypes which should be an array like this:

$allowed_filetypes = array('jpg', 'jpeg');

Read the above link with the examples and try to rewrite your script.

Regarding security

The only method to be sure, is to remove the code. When there is an embedded script, in case of a jpeg file, this is written in the FF FE bytes block, which is the area used by the softwares to save comments, metadata and other stuff:

An easy method is to use commands like jpetran:

jpeptran -copy none original.jpg > new.jpg

Otherwise, you could read the file, byte per byte and remove all the contents from FF FE to the next marker.

A part this, place the original file inside a directory that is not accessible to the user. Serve a resized image, the resizing process most of the times will remove the comment block, but pay attention to this:

convert -thumbnail 80x80 source.jpg new_thumb.jpg

It's using ImageMagick, if I upload a file of 100x100 it will be resized and the comment block will be stripped off, so the code will be removed, but if I send an image of the same sizes of the command (80x80), the file will be just copied to the new name and the comment block will not be removed. To be sure comments are removed add -strip:

convert -strip -thumbnail 80x80 source.jpg new_thumb.jpg

The same happens with the Imagick library in PHP:

$img = new Imagick();
$img->readImage('evil.jpg');
$img->resizeImage(80, 80, Imagick::FILTER_LANCZOS, 1);
$img->writeImage('newimg.jpg');
$img->clear();
$img->destroy();

To remove the comments add $img->stripImage() after the readImage() method.

The GD library, instead, will remove the embedded script:

$im = imagecreatetruecolor('80', '80');
$image = imagecreatefromjpeg('evil.jpg');
imagecopyresized($im, $image, 0, 0, 0, 0, 80, 80, 80, 80);
imagejpeg($im, './new100gd.jpg', 100);

So pay attention to these details, and setup an .htaccess file for the directories that will save the images and add this instruction:

php_flag engine off

Check this thread for some suggestions:

Hope is useful, bye!

Edited by cereal

0

thnx i have updated the code with the sollution you mentioned, but i am getting messege
Opps! Image Format not allowed!

here is my modified code:

<?php
$uploaddir = "./images";
$userfile = $_FILES['user_profile_image'];
$allowed_filetypes = array('jpg', 'jpeg');


   $maxsize = 2097152; // Maximum filesize in BYTES (currently 2MB).
   $upload_path = $uploaddir; // The place the files will be uploaded to (currently a 'files' directory).
   $filename = $_FILES['user_profile_image']['name']; // Get the name of the file (including file extension).
   $ext = substr($filename, strpos($filename,'.'), strlen($filename)-1); // Get the extension from the filename.

   // Check if the filetype is allowed, if not DIE and inform the user.
   if(!in_array($ext,$allowed_filetypes)){
      echo 'Opps! Image Format not allowed!';
    exit;
 }
list($width,$height,$type,$attr) = getimagesize($_FILES['user_profile_image']['tmp_name']);
$mime = image_type_to_mime_type($type);
if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png")) {
die("<BR><BR>Error3: Upload file type un-recognized. Only .JPG or .PNG images allowed.");
}
   // Now check the filesize, if it is too large then DIE and inform the user.
   if(filesize($_FILES['photo1']['tmp_name']) > $max_filesize){
    echo 'Opps! Image is to big!';
    exit;
 }
   // Check if we can upload to the specified path, if not DIE and inform the user.
   if(!is_writable($upload_path)){
      die('You cannot upload to the specified directory, please CHMOD it to 777.');
 }
 //This line assigns a random number to a variable. You could also use a timestamp here if you prefer. 
$ran = rand () ;
//This takes the random number (or timestamp) you generated and adds a . on the end, so it is ready of the file extension to be appended.
$ran2 = $ran;
//This assigns the subdirectory you want to save into... make sure it exists!
$target = "./images";
//This combines the directory, the random file name, and the extension
$target = $target . $ran2.$ext; 
if(move_uploaded_file($_FILES['user_profile_image']['tmp_name'], $target)) 
{
echo "<p>Your image was successfully uploaded!</p>
<p><strong>Forums</strong><br />
  <input name=\"textfield\" type=\"text\" id=\"textfield\" size=\"75\"   value=\"[URL=http://".$siteurl."][IMG]http://".$siteurl.$uploaddir.$ran2.$ext."[/img][/URL]\"/>
</p>
<p><strong>Forums (2)</strong><br />
  <input name=\"textfield2\" type=\"text\" id=\"textfield2\" size=\"75\"  value=\"[url=http://".$siteurl."][img=".$siteurl.$uploaddir.$ran2.$ext."][/url]\"/>
</p>
<p><strong>Direct Link</strong><br />
  <input name=\"textfield4\" type=\"text\" id=\"textfield4\" value=\"".$siteurl.$uploaddir.$ran2.$ext."\" size=\"75\" /><p>
";
}
else
{
    echo 'Opps! Looks like we have a problem....<br><br>';
    echo '<b>Error: <FONT COLOR="#FF3300"><u>There was a weird error!</u></b></font>';
    exit;
exit;
}
?>

is there still any modification left??

0

Ok, in practice at line 10 you get the extension with the dot (.jpg), you can add them to the array values:

$allowed_filetypes = array(
    '.jpg',
    '.jpeg'
);

Or you can change line 10 with this:

$ext = strtolower(pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION));

In case the file submitted is without an extension, the above will return an empty string. While the original script you are using, will return part of the file name.

Try with this modification and then, if you have still difficults to solve the problems, let us know.

Docs about pathinfo(): http://www.php.net/manual/en/function.pathinfo.php

Note for the record: there are few typos in my previous post, the name of the program was jpegtran, neither jpetran nor jpeptran...

Edited by cereal

0

Do you mean you get the same error or is different? Can you show the name of the file? E.g.: image.jpg.

Also, if this file is a PNG add the extension to the array:

$allowed_filetypes = array(
    'jpg',
    'jpeg',
    'jpe',
    'png'
);

Otherwise the script will continue to accept only files with jpg or jpeg extension.

0

Yup it gives me same error. And the file is jpg

I will post the name later, as i am not near my pc right now.

I think the name was something like "testing-perfect.jpg"

Edited by Eagle.Avik

0

Ok, check it, in the meantime this is my test:

<?php

if($_FILES)
{

    $filename = $_FILES['user_profile_image']['name'];
    $allowed_filetypes = array(
        'jpg',
        'jpeg',
        'jpe',
        'png'
    );

    $ext = strtolower(pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION));

    if( ! in_array($ext, $allowed_filetypes)) { echo 'not allowed'; }
    else { echo $filename . "<br />"; }

    echo 'POST:<pre>';
    print_r($_POST);
    echo '</pre>';

    echo 'FILES:<pre>';
    print_r($_FILES);
    echo '</pre>';

}

?>

<form method="post" action="" enctype="multipart/form-data">
    <input type="file" name="user_profile_image" />
    <input type="submit" name="submit" value="upload" />
</form>

And works fine. Try it by yourself, and check what you get in the $_FILES array, you can paste the result here.

0

okay i did the test and here is what i get:

test-perfect.jpg
POST:
Array
(
)
FILES:
Array
(
    [user_profile_image] => Array
        (
            [name] => test-perfect.jpg
            [type] => image/jpeg
            [tmp_name] => E:\wamp\tmp\phpD4DC.tmp
            [error] => 0
            [size] => 32574
        )

)
0

Ok, as you see you got the $_FILES array and the validation went through. Now this is yours:

<?php

if($_FILES)
{   
    $uploaddir = "./images";
    $userfile = $_FILES['user_profile_image'];
    $allowed_filetypes = array('.jpg', '.jpeg');
    $maxsize = 2097152;
    $upload_path = $uploaddir;
    $filename = $_FILES['user_profile_image']['name'];
    $ext = substr($filename, strpos($filename,'.'), strlen($filename)-1);

    if(!in_array($ext,$allowed_filetypes)){
        echo 'Opps! Image Format not allowed!';
        exit;
     }

    echo 'POST:<pre>';
    print_r($_POST);
    echo '</pre>';

    echo 'FILES:<pre>';
    print_r($_FILES);
    echo '</pre>';

}

?>

<form method="post" action="" enctype="multipart/form-data">
    <input type="file" name="user_profile_image" />
    <input type="submit" name="submit" value="upload" />
</form>

The only adjustment I had to make was in the $allowed_filetypes array, by adding the dots to the values.

This is the version with my suggestions:

<?php

if($_FILES)
{

    $uploaddir = "./images";
    $userfile = $_FILES['user_profile_image'];
    $allowed_filetypes = array(
        'jpg',
        'jpeg',
        'jpe',
        'png'
    );
    $maxsize = 2097152;
    $upload_path = $uploaddir;
    $filename = $_FILES['user_profile_image']['name'];
    $ext = strtolower(pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION));

    if(!in_array($ext,$allowed_filetypes)){
        echo 'Opps! Image Format not allowed!';
        exit;
     }

    echo 'POST:<pre>';
    print_r($_POST);
    echo '</pre>';

    echo 'FILES:<pre>';
    print_r($_FILES);
    echo '</pre>';

}

?>

<form method="post" action="" enctype="multipart/form-data">
    <input type="file" name="user_profile_image" />
    <input type="submit" name="submit" value="upload" />
</form>

If you still don't get it to work, post your updated version of the script.

0

thanx i updated the script with database connection so that i can store the path of the images, it works!

but how do i resize the image maintaining the aspect ratio??
and is this script really safe??

here is my modified script:

<?php
include('../loginssn.php'); 

if($_FILES)
{
    $uploaddir = "./images/";
    $userfile = $_FILES['user_profile_image'];
    $allowed_filetypes = array(
        'jpg',
        'jpeg',
        'jpe',
        'png'
    );
    $maxsize = 102400;// only 100kb
    $upload_path = $uploaddir;
    $filename = $_FILES['user_profile_image']['name'];
    $ext = strtolower(pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION));
    if(!in_array($ext,$allowed_filetypes)){
        echo 'Opps! Image Format not allowed!';
        exit;
     }

    list($width,$height,$type,$attr) = getimagesize($_FILES['user_profile_image']['tmp_name']);
    $mime = image_type_to_mime_type($type);
    if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png")) {
    die("<BR><BR>Error3: Upload file type un-recognized. Only .JPG or .PNG images allowed.");
    }


   if(filesize($_FILES['user_profile_image']['tmp_name']) > $maxsize){
    echo 'Opps! Image is to big!';
    exit;

   }

   if(!is_writable($upload_path)){
   die('You cannot upload to the specified directory, please CHMOD it to 777.');
   }


$image_name =strip_tags($_SESSION['username']);


$filename = "./images/" .$image_name.".png";
  if (file_exists($filename)) {
    unlink($filename);
  }


$image_upload_dir = $uploaddir . $image_name . "." . $ext; 

if(move_uploaded_file($_FILES['user_profile_image']['tmp_name'], $image_upload_dir)) 
{
$image_final_destionation = $rooturl ."user/images/" . $image_name .".". $ext;

      try
      {
      $pdo = new PDO('mysql:host=localhost;dbname=users', 'Avik', '');
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }  
      try
      {
      $sql = 'UPDATE user_login_info SET user_image = :user_image WHERE user_id = :id';
      $statement = $pdo->prepare($sql);
      $statement->bindValue(':id', $_SESSION['user_id']);
      $statement->bindValue(':user_image', $image_final_destionation);
      $statement->execute();
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }

echo "<p>Your image was successfully uploaded!</p>
<p><strong>URL:</strong><br />
  <input name=\"textfield\" type=\"text\" id=\"textfield\" size=\"75\"   value=\"". $rooturl ."user/images/" . $image_name .".". $ext ."\"/>
</p>

";
}


else
{
    echo 'Opps! Looks like we have a problem....<br><br>';
    echo '<b>Error: <FONT COLOR="#FF3300"><u>There was a weird error!</u></b></font>';
    exit;
exit;
}

    echo 'FILES:<pre>';
    print_r($_FILES);
    echo '</pre>';
}


?>
0

You can use Imagick:

$tmpfile = $_FILES['user_profile_image']['tmp_name'];
$dest = './images/'.sha1($_SESSION['username']).'.png';

$img = new Imagick();
$img->readImage($tmpfile);
$img->stripImage();
$img->scaleImage(100, 100, true);
$img->writeImage($dest);
$img->clear();
$img->destroy();

Where the first parameter in the scaleImage() method is for the width, and the second for the height. The third parameter is used in case the uploaded images is under the defined sizes, for example here we have 100x100, while the uploaded image is 80x80, this will be resized to 100x100, if you set false then you get 80x80.

By using the temporary file in the $_FILES array, you can avoid to use move_uploaded_file() unless you want to store the original file somewhere.

Since you're using the username as filename you can, also, avoid to store it into the table database, just use the hash of the username. In my example I'm using sha1() which will return a 40 hexadecimal string, so the range will be based on a-f0-9 (i.e. 16 alphanumeric characters). When you want to display the avatars you do:

# from session
echo '<img src="/images/'. sha1($_SESSION['username']) .'.png" />';

# manually
echo '<img src="/images/'. sha1('Eagle.Avik') .'.png" />';

# or from query result set in while loop
echo '<img src="/images/'. sha1($row['username']) .'.png" />';

About Security. I don't know the contents of the included file (loginssn.php) nor I see how $rooturl is defined, as far as I see the script is enough secure. I would add a check to verify if user_profile_image exists in the $_FILES array, because if a client sends a cURL request with the wrong fieldname it will generate an unexpected error. Also I don't know how the included file replies to a connection that does not provide a valid session. And I would check the width and the height of the image because, especially when using the GD library, you can hit the memory limit, if for example the script tries to resize an image with more than 20000 pixels per side or even less if the memory limit is low.

As explained in my previous post, by creating the resized image you can remove the comment block and any included script, but if you decide to save the original file then you have to perform the same operation: clean the comment blocks. The best would be to save it outside the webroot.

Side Note

If you're planning to deal with more than 10000 profile images then it's a good idea to split the images to different paths. A method is to create multiple directories basing on the filename. Since in my example I'm using an exadecimal hash, you could create the directories basing on the first two characters, that would create 256 directories (i.e. 16^2). I developed an example, if you want to try it check here:

Edited by cereal

0

i think i dont have imagick installed. i searched a bit and found that i have gd library installed as default which does the same. can you use gd instead of imagick, i dont want to install another library which does the same job(probably much more functionality that gd).

0

well here is my code using gd, i have to rewrite a new script.
dont know if it is safe enough, what do you think is there any security gaps? is it better than the previous one?

<?php

 include('../loginssn.php'); 

if($_FILES)
{

define('THUMBNAIL_IMAGE_MAX_WIDTH', 200);
define('THUMBNAIL_IMAGE_MAX_HEIGHT', 200);


function generate_image_thumbnail($source_image_path, $thumbnail_image_path)
{
    list($source_image_width, $source_image_height, $source_image_type) = getimagesize($source_image_path);
    switch ($source_image_type) {
        case IMAGETYPE_GIF:
            $source_gd_image = imagecreatefromgif($source_image_path);
            break;
        case IMAGETYPE_JPEG:
            $source_gd_image = imagecreatefromjpeg($source_image_path);
            break;
        case IMAGETYPE_PNG:
            $source_gd_image = imagecreatefrompng($source_image_path);
            break;
    }
    if ($source_gd_image === false) {
        return false;
    }
    if($source_image_width > $source_image_height){
    $extra_image_remain = $source_image_width - $source_image_height;
    $cropped_image_width = $source_image_width - $extra_image_remain;
    $cropped_image_height = $source_image_height;
    }
    else if($source_image_width < $source_image_height){
    $extra_image_remain = $source_image_height - $source_image_width;
    $cropped_image_width = $source_image_width;
    $cropped_image_height = $source_image_height - $extra_image_remain;
    }
    else if($source_image_width == $source_image_height)
    {
    $extra_image_cent = $source_image_width - $source_image_height;
    $cropped_image_width = $source_image_width;
    $cropped_image_height = $source_image_height;
    }


    $cropped_image = imagecreatetruecolor($source_image_width, $source_image_height);
    imagecopyresampled($cropped_image, $source_gd_image, 0, 0, 0, 0, $cropped_image_width, $cropped_image_height, $source_image_width, $source_image_height);


    $source_aspect_ratio = $cropped_image_width / $cropped_image_height;
    $thumbnail_aspect_ratio = THUMBNAIL_IMAGE_MAX_WIDTH / THUMBNAIL_IMAGE_MAX_HEIGHT;
    if ($cropped_image_width <= THUMBNAIL_IMAGE_MAX_WIDTH && $cropped_image_height <= THUMBNAIL_IMAGE_MAX_HEIGHT) {
        $thumbnail_image_width = $cropped_image_width;
        $thumbnail_image_height = $cropped_image_height;
    } elseif ($thumbnail_aspect_ratio > $source_aspect_ratio) {
        $thumbnail_image_width = (int) (THUMBNAIL_IMAGE_MAX_HEIGHT * $source_aspect_ratio);
        $thumbnail_image_height = THUMBNAIL_IMAGE_MAX_HEIGHT;
    } else {
        $thumbnail_image_width = THUMBNAIL_IMAGE_MAX_WIDTH;
        $thumbnail_image_height = (int) (THUMBNAIL_IMAGE_MAX_WIDTH / $source_aspect_ratio);
    }


    $thumbnail_gd_image = imagecreatetruecolor($thumbnail_image_width, $thumbnail_image_height);
    imagecopyresampled($thumbnail_gd_image, $source_gd_image, 0, 0, 0, 0, $thumbnail_image_width, $thumbnail_image_height, $cropped_image_width, $cropped_image_height);


    imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 90);
    imagedestroy($source_gd_image);
    imagedestroy($thumbnail_gd_image);
    return true;
}

/*
 * Uploaded file processing function
 */

define('UPLOADED_IMAGE_DESTINATION', './images/');


function process_image_upload($field)
{
    $temp_image_path = $_FILES[$field]['tmp_name'];
    $image_name = sha1(strip_tags($_SESSION['username']));


    list(, , $temp_image_type) = getimagesize($temp_image_path);
    if ($temp_image_type === NULL) {
        return false;
    }
    switch ($temp_image_type) {
        case IMAGETYPE_GIF:
            break;
        case IMAGETYPE_JPEG:
            break;
        case IMAGETYPE_PNG:
            break;
        default:
            return false;
    }

    $uploaded_image_path = UPLOADED_IMAGE_DESTINATION . preg_replace('{\\.[^\\.]+$}', '.jpg', $image_name. '.jpg');
    move_uploaded_file($temp_image_path, $uploaded_image_path);
    $result = generate_image_thumbnail($uploaded_image_path, $uploaded_image_path);

    $rooturl= "http://" . $_SERVER["SERVER_NAME"] . "/";
    $image_final_destionation =  $rooturl ."user/images/" . $image_name .".jpg";

     try
      {
      $pdo = new PDO('mysql:host=localhost;dbname=users', 'Avik', '');
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }  
      try
      {
      $sql = 'UPDATE user_login_info SET user_image = :user_image WHERE user_id = :id';
      $statement = $pdo->prepare($sql);
      $statement->bindValue(':id', $_SESSION['user_id']);
      $statement->bindValue(':user_image', $image_final_destionation);
      $statement->execute();
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }

}

/*
 * Here is how to call the function(s)
 */

$result = process_image_upload('user_profile_image');

if ($result === false) {
    echo 'An error occurred while processing upload';
} else {
    echo "<p>Your image was successfully uploaded!</p>
";
}
}
?>
<form action="" method="post" enctype="multipart/form-data">
    Upload an image for processing<br>
    <input type="file" name="user_profile_image" ><br>
    <input type="submit" value="Upload">
</form>

Edited by Eagle.Avik

0

It seems fine, but I would fix few things.

Change line 84 and 85 with these:

$temp_image_path = $_FILES[$field]['tmp_name'];
if( ! array_key_exists($field, $_FILES)) return false;
$image_name = sha1($_SESSION['username']);

With the IF condition we check if the $field is defined in the $_FILES array, currently seems safe and process_image_upload() returns false, but only because getimagesize() fails.

Also, you shouldn't need the strip_tags() function with sha1() because it will generate an hash and if you get the data from $_SESSION it should be already safe. But you can use it if you prefer it. Mine is just a suggestion.

If you want to use the GD library then you have to check the pixels of the original image, I see you have defined the sizes of the thumbnail but the problem is that the GD library assigns 4 bytes for each pixel of the image (RGB + Alpha channel) and if the user sends an image of 3000 pixels per side you get:

((3000 * 3000 * 4)/1024)/1024

which returns 34MB, if 8000 per side you get 244MB. In these cases if the memory limit is low the script will be interrupted. To check the current limit use:

echo ini_get('memory_limit');

You get the value in bytes, if you get -1 there is no limit and so the script will not crash. If instead there is one, calculate the maximum pixels you can support and use them as hard limit, for example if the memory limit is 32MB:

echo floor(sqrt((32*1024*1024)/4));

You can allow not more than 2896 pixels per side. So, to avoid this problem, change line 88:

list(, , $temp_image_type) = getimagesize($temp_image_path);

By adding width and height:

list($width, $height, $temp_image_type) = getimagesize($temp_image_path);

And check them:

if($width > 2896) return false;
if($height > 2896) return false;

About the $rooturl, you're using $_SERVER['SERVER_NAME'] and this is ok if your server is configured with:

UseCanonicalName = On

Because it will read the configuration file of the virtual host. But if this directive is Off then it will use the value sent from the client request, this is not secure, I could use it to create a link to an external website and pull an image from remote.

If you want to save the filename to the database you don't need the entire link, just the path if this differs for some reason.

References:

Bye!

0

Or, instead of:

if($width > 2896) return false;
if($height > 2896) return false;

You could use:

if($width * $height > 8388608) return false;

Where 8388608 is 32*1024*1024/4 and allows much more flexibility, because we are going to check the total number of pixels instead of the single sizes, and so a user can upload an image of 3000x1000...

Edited by cereal

0

i have modified the script again, this time i also allow user to crop the images themselves with js, and the user do not crop the image the script will do the job.

for client size resizing i am using imgareaselect (their url: http://odyniec.net/projects/imgareaselect/)

and i also have to write a script so that if the user use the file input and select a image, the image will automatically show on client browser, and can crop with imgareaselect.
if the user crop it will use the coordinate of cropping on the serverside to make the changes.

here is some snapshots i taken, you can see the cropping, and also after upload:

http://sdrv.ms/1c4mjH7

suggest any security holes and if the code could be optimized.

here is my final script:

<?php
include('../loginssn.php'); 

if($_FILES){
function generate_image_thumbnail($source_image_path, $thumbnail_image_path)
{
list($source_image_width, $source_image_height, $source_image_type) = getimagesize($source_image_path);
    switch ($source_image_type) {
        case IMAGETYPE_GIF:
            $source_gd_image = imagecreatefromgif($source_image_path);
            break;
        case IMAGETYPE_JPEG:
            $source_gd_image = imagecreatefromjpeg($source_image_path);
            break;
        case IMAGETYPE_PNG:
            $source_gd_image = imagecreatefrompng($source_image_path);
            break;
    }
    if ($source_gd_image === false) {
        return false;
    }

    //define what image size i will save
    define('THUMBNAIL_IMAGE_MAX_WIDTH', 100);
    define('THUMBNAIL_IMAGE_MAX_HEIGHT', 100);

    //if the user cropped the image
    define('RESIZE_IMAGE_MAX_WIDTH', 550);
    define('RESIZE_IMAGE_MAX_HEIGHT', 550);


    //if user mention where to crop
    if ($_POST['x1'] >= 0 && $_POST['y1'] >= 0 && $_POST['h'] > 0 && $_POST['w'] > 0)
    {
    //user input data from image cropping.
    $x1 = $_POST['x1']; //x co-ordinate of the image cropping
    $y1 = $_POST['y1']; //y co-ordinate of the image cropping
    $usercrop_image_height = $_POST['h']; //cropped image height
    $usercrop_image_width = $_POST['w'];  //cropped image width

    //maintaining the aspect ration of the image we get from user.
    $source_aspect_ratio = $source_image_width / $source_image_height;
    $thumbnail_aspect_ratio = 1 / 1;
    if ($source_image_width <= RESIZE_IMAGE_MAX_WIDTH && $source_image_height <= RESIZE_IMAGE_MAX_HEIGHT) {
        $resized_image_width = $source_image_width;
        $resized_image_height = $source_image_height;
    } elseif ($thumbnail_aspect_ratio > $source_aspect_ratio) {
        $resized_image_width = (int) (RESIZE_IMAGE_MAX_WIDTH * $source_aspect_ratio);
        $resized_image_height = RESIZE_IMAGE_MAX_HEIGHT;
    } else {
        $resized_image_width = RESIZE_IMAGE_MAX_WIDTH;
        $resized_image_height = (int) (RESIZE_IMAGE_MAX_HEIGHT / $source_aspect_ratio);
    }
    //resize and cropping the image for best fit.
    $resized_image_bg = imagecreatetruecolor($resized_image_width, $resized_image_height);
    imagecopyresampled($resized_image_bg, $source_gd_image, 0, 0, 0, 0, $resized_image_width, $resized_image_height, $source_image_width, $source_image_height);
    $crop_image_bg = imagecreatetruecolor(THUMBNAIL_IMAGE_MAX_WIDTH, THUMBNAIL_IMAGE_MAX_HEIGHT);
    imagecopyresampled($crop_image_bg, $resized_image_bg, 0, 0, $x1, $y1, THUMBNAIL_IMAGE_MAX_WIDTH, THUMBNAIL_IMAGE_MAX_HEIGHT, $usercrop_image_width, $usercrop_image_height);

  }
  //otherwise it will automatically generate and crop while maintaining aspect ratio.
  else
  {
    if($source_image_width > $source_image_height){
    $extra_image_remain = $source_image_width - $source_image_height;
    $cropped_image_width = $source_image_width - $extra_image_remain;
    $cropped_image_height = $source_image_height;
    }
    else if($source_image_width < $source_image_height){
    $extra_image_remain = $source_image_height - $source_image_width;
    $cropped_image_width = $source_image_width;
    $cropped_image_height = $source_image_height - $extra_image_remain;
    }
    else if($source_image_width == $source_image_height)
    {
    $extra_image_cent = $source_image_width - $source_image_height;
    $cropped_image_width = $source_image_width;
    $cropped_image_height = $source_image_height;
    }
    $cropped_image = imagecreatetruecolor($source_image_width, $source_image_height);
    imagecopyresampled($cropped_image, $source_gd_image, 0, 0, 0, 0, $cropped_image_width, $cropped_image_height, $source_image_width, $source_image_height);
    $source_aspect_ratio = $cropped_image_width / $cropped_image_height;
    $thumbnail_aspect_ratio = THUMBNAIL_IMAGE_MAX_WIDTH / THUMBNAIL_IMAGE_MAX_HEIGHT;
    if ($cropped_image_width <= THUMBNAIL_IMAGE_MAX_WIDTH && $cropped_image_height <= THUMBNAIL_IMAGE_MAX_HEIGHT) {
        $thumbnail_image_width = $cropped_image_width;
        $thumbnail_image_height = $cropped_image_height;
    } elseif ($thumbnail_aspect_ratio > $source_aspect_ratio) {
        $thumbnail_image_width = (int) (THUMBNAIL_IMAGE_MAX_HEIGHT * $source_aspect_ratio);
        $thumbnail_image_height = THUMBNAIL_IMAGE_MAX_HEIGHT;
    } else {
        $thumbnail_image_width = THUMBNAIL_IMAGE_MAX_WIDTH;
        $thumbnail_image_height = (int) (THUMBNAIL_IMAGE_MAX_WIDTH / $source_aspect_ratio);
    }
    $crop_image_bg = imagecreatetruecolor($thumbnail_image_width, $thumbnail_image_height);
    imagecopyresampled($crop_image_bg, $source_gd_image, 0, 0, 0, 0, $thumbnail_image_width, $thumbnail_image_height, $cropped_image_width, $cropped_image_height);

  }


    $thumbnail_gd_image = $crop_image_bg ;
    imagejpeg($thumbnail_gd_image, $thumbnail_image_path, 90);
    imagedestroy($source_gd_image);
    imagedestroy($thumbnail_gd_image);
    return true;
}


//Uploaded file processing function
define('UPLOADED_IMAGE_DESTINATION', './images/');


function process_image_upload($field)
{
$temp_image_path = $_FILES[$field]['tmp_name'];
if( ! array_key_exists($field, $_FILES)) return false;
$image_name = sha1($_SESSION['username']);


    list($width, $height, $temp_image_type) = getimagesize($temp_image_path);
    if($width * $height > 8388608) return false;
    if ($temp_image_type === NULL) {
        return false;
    }
    switch ($temp_image_type) {
        case IMAGETYPE_GIF:
            break;
        case IMAGETYPE_JPEG:
            break;
        case IMAGETYPE_PNG:
            break;
        default:
            return false;
    }

    $uploaded_image_path = UPLOADED_IMAGE_DESTINATION . preg_replace('{\\.[^\\.]+$}', '.jpg', $image_name. '.jpg');
    move_uploaded_file($temp_image_path, $uploaded_image_path);
    $result = generate_image_thumbnail($uploaded_image_path, $uploaded_image_path);

    $rooturl= "http://" . $_SERVER["SERVER_NAME"] . "/"; //url of your domain
    $image_final_destionation =  $rooturl ."user/images/" . $image_name .".jpg";

    //connect to your database.
     try
      {
      $pdo = new PDO('mysql:host=localhost;dbname=users', 'Avik', '');
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }
      //now for saving the path into the table.
      try
      {
      $sql = 'UPDATE user_login_info SET user_image = :user_image WHERE user_id = :id';
      $statement = $pdo->prepare($sql);
      $statement->bindValue(':id', $_SESSION['user_id']);
      $statement->bindValue(':user_image', $image_final_destionation);
      $statement->execute();
      }
      catch (PDOException $e)
      {
      $output = 'Unable to connect to the database server.';
      echo $output;
      exit();
      }

}


//Here is how to call the function(s)

$result = process_image_upload('user_profile_image');

if ($result === false) {
    echo 'An error occurred while processing upload<br/>Image size should not be more than 1.5MB<br/>Image dimension should not be more than 1700*1700';
} else {
header('Location: '.$rooturl.'user');
}
}
?>

thnx.

0

As I wrote in my previous post, this is not secure:

$rooturl= "http://" . $_SERVER["SERVER_NAME"] . "/";

PHP documentation says:

SERVER_NAME
The name of the server host under which the current script is executing. If the script is running on a virtual host, this will be the value defined for that virtual host.

So it depends on the Apache configuration, the problems comes in here: in misconfigured servers the host name can be overwritten by the client header because the UseCanonicalName directive is Off by default:

With UseCanonicalName Off Apache will form self-referential URLs using the hostname and port supplied by the client if any are supplied (otherwise it will use the canonical name, as defined above).

How this affects your script

Set up a page (name.php) with only this:

echo "http://".$_SERVER['SERVER_NAME']."/";

Then from another script run this code:

<?php

$url = "http://good.dev/name.php";
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('HOST: evil.dev'));
curl_setopt($ch, CURLOPT_PROXY, "http://good.dev:80");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$result = curl_exec($ch);
curl_close ($ch);

print_r($result);

You expect to get http://good.dev/, i.e. your domain name, but you can get http://evil.dev/. Depending on the configuration of the server.

Since you're saving the absolute url into the database, if I can manipulate $_SERVER['SERVER_NAME'] I can create a link like this one:

http://evil.dev/users/images/filename.png 

The relative path is the same, the name of the file too, but the domain is different. Old browsers can run javascript in the src attribute of the img tag.

If you use $_SERVER['SERVER_NAME'] in other contexts, for example:

<script type="text/css" src="http://<?php echo $_SERVER['SERVER_NAME']; ?>/js/jquery.js"></script>

And cache the pages, then with the above script the attacker can pollute the cache and make the other users open a remote javascript.

The $_SERVER['HTTP_HOST'] variable is even worse because it does not depend on the UseCanonicalName. In these cases the correct solution is to create a config file in which you save the base url of the current website, for example:

$config['base_url'] = 'http://good.dev/';

Keep in mind that the same problem occurs also with the mod_rewrite module of Apache when you use variables as {HTTP_HOST}.

Hope it helps.

This topic has been dead for over six months. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.