1
\$\begingroup\$

I wrote code to upload multiple(2) files.

1.Check if this is image: mime/extension/getimagesize. 2.Remove execute file permissions from the file(chmod).

Is this script secure? What can go wrong?

Class photograph

class Photograph extends DatabaseObject {

    protected static $table_name = "photographs";
    protected static $db_columns = ['id', 'filename', 'filename2', 'type', 'type2', 'size', 'size2', 'caption'];

    public $id;
    public $filename;
    public $filename2;
    public $type;
    public $type2;
    public $size;
    public $size2;
    public $caption;

    private $temp_path;

    //protected $upload_dir="../images";
    protected $upload_dir = PUBLIC_PATH . '/images';

    protected $max_file_size = 1048576; // 1 MB expressed in bytes

    public $errors = array();

    // Define allowed filetypes to check against during validations
    protected $allowed_mime_types = ['image/png', 'image/gif', 'image/jpg', 'image/jpeg'];

    protected $allowed_extensions = ['png', 'gif', 'jpg', 'jpeg'];

    protected $upload_errors = array(
        0 => 'There is no error, the file uploaded with success.',
        1 => 'Larger than upload_max_filesize.',
        2 => 'Larger than form MAX_FILE_SIZE.',
        3 => 'Partially uploaded.',
        4 => 'No file.',
        6 => 'No temporary folder.',
        7 => 'Failed to write file to disk.',
        8 => 'A PHP extension stopped the file upload.',
    );

    protected function sanitize_file_name($string) {
    // Remove characters that could alter file path.
    // I disallowed spaces because they cause other headaches.
    // "." is allowed (e.g. "photo.jpg") but ".." is not.
    $string = preg_replace("/([^A-Za-z0-9_\-\.]|[\.]{2})/", "", $string);
    // basename() ensures a file name and not a path
    $string = basename($string);
    return $string;
    }

    protected $check_is_image = true;

    private function size_as_kb() {
        if ($this->size < 1024) {
            return "{$this->size} bytes";
        } elseif ($this->size < 1048576) {
            $size_kb = round($this->size/1024);
            return "{$size_kb} KB";
        } else {
            $size_mb = round($this->size/1048576, 1);
            return "{$size_mb} MB";
        }
    }

    private function size_as_kb_2() {
        if ($this->size2 < 1024) {
            return "{$this->size2} bytes";
        } elseif ($this->size2 < 1048576) {
            $size_kb = round($this->size2/1024);
            return "{$size_kb} KB";
        } else {
            $size_mb = round($this->size2/1048576, 1);
            return "{$size_mb} MB";
        }
    }

    // Returns the file permissions in octal format.
    private function file_permissions($file) {
        // fileperms returns a numeric value
        $numeric_perms = fileperms($file);
        // but we are used to seeing the octal value
        $octal_perms = sprintf('%o', $numeric_perms);
        return substr($octal_perms, -4);
    }


/* ==================================================================================
                        public function attach_file($file)
================================================================================== */

    public function attach_file($file) {

          $this->temp_path   = $file['tmp_name']['0'];
          $this->filename    = $this->sanitize_file_name($file['name']['0']);
          $this->type        = $file['type']['0'];
          $this->size        = $file['size']['0'];

          $this->temp_path2  = $file['tmp_name']['1'];
          $this->filename2   = $this->sanitize_file_name($file['name']['1']);
          $this->type2       = $file['type']['1'];
          $this->size2       = $file['size']['1'];

    }
/* ==================================================================================
                        public function upload file 1
================================================================================== */

public function upload_file() {

    $filename_extension = pathinfo($this->filename, PATHINFO_EXTENSION);

    if($this->size > $this->max_file_size) {
        // PHP already first checks php.ini upload_max_filesize, and 
        // then form MAX_FILE_SIZE if sent.
        // But MAX_FILE_SIZE can be spoofed; check it again yourself.
        echo "Error: Your product image 1 is too big. The file has: {$this->size_as_kb()}.<br />";
        return false;
    }

    if(!is_uploaded_file($this->temp_path)) {
        echo "Fatal error: Possible alien attack!<br />";
        return false;
    }

    // check if this is image: mime/extension/getimagesize - return false.
    if(!in_array($this->type, $this->allowed_mime_types)) {
        echo "Error: Not an allowed mime type.<br />";  
    }

    if(!in_array($filename_extension, $this->allowed_extensions)){
        echo "Error: Your product image 1 file extension is not supported.<br />";

    }

    if($this->check_is_image && (getimagesize($this->temp_path) === false)) {
        // getimagesize() returns image size details, but more importantly,
        // returns false if the file is not actually an image file.
        // You obviously would only run this check if expecting an image.
        echo "Error: Not a valid image file.<br />";
        return false;
    }
    // ends - check image

    // Can't save without filename and temp location
    if(empty($this->filename) || empty($this->temp_path) ) {
        $this->errors[] = "The file location was not available.";
        return false;
    }
    $target_path = $this->upload_dir . '/' . $this->filename;

    // Make sure a file doesn't already exist in the target location
    if(file_exists($target_path)) {
        $this->errors[] = "The file {$this->filename} already exists.";
        return false;
    }
    // Attempt to move the file 
    if(move_uploaded_file($this->temp_path, $target_path)) {
        echo "First file name is '{$this->filename}'.<br />";
        echo "First file moved to: {$target_path}<br />" ;
        echo "Uploaded file size was {$this->size_as_kb()}.<br />";
        echo "<br />";
        // Success  

        // remove execute file permissions from the file
        if(chmod($target_path, 0644)) {
            echo "Execute permissions removed from file: {$this->filename}.<br />";
            $file_permissions = $this->file_permissions( $target_path );
            echo "File permissions are now '{$file_permissions}'.<br />";
            }
            else {
                echo "Error: Execute permissions could not be removed.<br />";
            }
            echo "<br />";

    } else {
        // File was not moved.
    $this->errors[] = "The file upload failed, possibly due to incorrect permissions on the upload folder.";
        return false;
    }       

}

/* ==================================================================================
                        public function upload file 2
================================================================================== */

public function upload_file2() {

    // Can save first without second filename and temp location
    if(!empty($this->filename2) || !empty($this->temp_path2)) { 

        $filename_extension2 = pathinfo($this->filename2, PATHINFO_EXTENSION);

        if($this->size2 > $this->max_file_size) {
            // PHP already first checks php.ini upload_max_filesize, and 
            // then form MAX_FILE_SIZE if sent.
            // But MAX_FILE_SIZE can be spoofed; check it again yourself.
            echo "Error: Your product image 2 is too big. The file has: {$this->size_as_kb_2()}.<br />";
            return false;
        }

        if(!is_uploaded_file($this->temp_path2)) {
            echo "Fatal error: Possible alien attack!<br />";
            return false;
        }

        // check if this is image: mime/extension/getimagesize - return false.
        if(!in_array($this->type2, $this->allowed_mime_types)) {
            echo "Error: Not an allowed mime type.<br />";  
        }

        if(!in_array($filename_extension2, $this->allowed_extensions)){
            echo "Error: Your product image 2 file extension is not supported.<br />";

        }

        if($this->check_is_image && (getimagesize($this->temp_path2) === false)) {
            // getimagesize() returns image size details, but more importantly,
            // returns false if the file is not actually an image file.
            // You obviously would only run this check if expecting an image.
            echo "Error: Not a valid image file.<br />";
            return false;
        }
        // ends - check image


        $target_path2 = $this->upload_dir . '/' . $this->filename2;

        // Make sure a file doesn't already exist in the target location
        if(file_exists($target_path2)) {
            $this->errors[] = "The file {$this->filename2} already exists.";
            return false;
        }
        // Attempt to move the file 
        if(move_uploaded_file($this->temp_path2, $target_path2)) {
            echo "Second file name is '{$this->filename2}'.<br />";
            echo "Second file moved to: '{$target_path2}'<br />" ;
            echo "Uploaded file size was {$this->size_as_kb_2()}.<br />";                   
            echo "<br />";
            // Success
            // remove execute file permissions from the file
            if(chmod($target_path, 0644)) {
                echo "Execute permissions removed from file: {$this->filename2}.<br />";
                $file_permissions2 = $this->file_permissions( $target_path2 );
                echo "File permissions are now '{$file_permissions2}'.<br />";
                }
                else {
                    echo "Error: Execute permissions could not be removed.<br />";
                }
                echo "<br />";


        } else {
            // File was not moved.
            $this->errors[] = "The file upload failed, possibly due to incorrect permissions on the upload folder.";
            return false;
        }
    }

}

/* ==================================================================================
                                public function save()
================================================================================== */

    public function save() {
        // A new record won't have an id yet.
    if(isset($this->id)) {

    $this->upload_file();

    $this->upload_file2();

    if($this->update_photo()) {
        // We are done with temp_path, the file isn't there anymore
        unset($this->temp_path, $this->temp_path2);
        return true;
            }
    } else {

    // Can't save if there are pre-existing errors
    if(!empty($this->errors)) { return false; }

    $this->upload_file();

    $this->upload_file2();  

    // Save a corresponding entry to the database
    //if($this->create3()) { - dziala
    if($this->create()) {
                // We are done with temp_path, the file isn't there anymore
                unset($this->temp_path, $this->temp_path2);
                return true;
}
}
}

photo_upload.php

if(isset($_POST['submit'])) {
        $photo = new Photograph();
        $photo->caption = $_POST['caption'];
        $photo->attach_file($_FILES['file_upload']);
        $photo->attach_file($_FILES['file_upload']);
        if($photo->save() ) {
            // Success
        } else {
            // Failure   
        }   
    }

<form action="photo_upload.php" enctype="multipart/form-data" method="POST">

        <p><input name="file_upload[]" type="file" id="file_upload[]" value=""></p>

        <p><input name="file_upload[]" type="file" id="file_upload[]" value=""></p>

        <p>Caption: <input type="text" name="caption" value="" /></p>
    <input type="submit" name="submit" value="Upload" />
  </form>
\$\endgroup\$
15
  • 1
    \$\begingroup\$ You want to know whether your script is secure. Secure against what? What is your threat model? \$\endgroup\$
    – Mast
    Nov 28, 2019 at 12:07
  • \$\begingroup\$ I want to know if this is a good practice. Chmod, filename_extension and so on. \$\endgroup\$ Nov 28, 2019 at 12:36
  • \$\begingroup\$ That has nothing to do with whether it's secure or not. What are your expectations for this code? \$\endgroup\$
    – Mast
    Nov 28, 2019 at 12:41
  • \$\begingroup\$ e.g that the hacked photo, script, file with 777 mode will not get to the server. \$\endgroup\$ Nov 28, 2019 at 12:52
  • 1
    \$\begingroup\$ You want to protect against hacked photos? \$\endgroup\$
    – Mast
    Nov 28, 2019 at 15:17

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.