Script De Téléchargement D'Image Sécurisé

je ne sais pas si cela va arriver, mais je vais essayer.

depuis une heure, j'ai fait des recherches sur la sécurité de téléchargement d'image. J'ai appris qu'il y avait beaucoup de fonctions pour tester le téléchargement.

dans mon projet, je dois être en sécurité avec les images téléchargées. Il peut également y avoir une très grande quantité de it et il peut nécessiter beaucoup de bande passante, donc l'achat d'une API n'est pas une option.

donc j'ai décidé d'obtenir un script PHP complet pour REALLY secure téléchargement d'image. Je pense aussi que cela aidera beaucoup de gens là-bas, parce qu'il est impossible d'en trouver un vraiment sûr. Mais je ne suis pas expert en php, donc c'est vraiment un casse-tête pour moi d'ajouter quelques fonctions, donc je vais demander l'aide de cette communauté pour créer un script complet de téléchargement d'image vraiment sécurisé.

Vraiment grands sujets qui sont ici (cependant, ils sont tout simplement dire ce qui est nécessaire pour faire le tour, mais pas comment le faire, et comme je l'ai dit je ne suis pas un maître en PHP, donc je je ne suis pas capable de le faire tout par moi-même): PHP image de téléchargement de sécurité liste de contrôle https://security.stackexchange.com/questions/32852/risks-of-a-php-image-upload-form

en résumé, ils disent que c'est ce qui est nécessaire pour le téléchargement d'image de sécurité (je vais citer à partir des pages ci-dessus):

  • empêche PHP d'exécuter dans le dossier de téléchargement en utilisant .httaccess.
  • N'autorise pas le téléchargement si le nom du fichier contient la chaîne de caractères"php".
  • N'autorisent que les extensions jpg,jpeg,gif et png.
  • permet seulement le type de fichier image.
  • Interdire image avec deux type de fichier.
  • changer le nom de l'image. Télécharger vers un sous-répertoire ne répertoire racine.

aussi:

  • image en utilisant GD (ou Imagick) et enregistrer l'image traitée. Tous les autres sont juste amusement ennuyeux pour les pirates"
  • comme rr l'a souligné, utilisez move_uploaded_file () pour tout téléchargement"
  • soit dit en passant, vous voudriez être très restrictif à propos de votre dossier de téléchargement. Ces endroits sont l'un des coins sombres où de nombreux exploits

    arriver. Ceci est valable pour tout type de téléchargement et toute programmation

    langue/serveur. Vérifier

    https://www.owasp.org/index.php/Unrestricted_File_Upload
  • Niveau 1: Vérifier l'extension (extension de fichier se termine avec)
  • Niveau 2: Cochez le type MIME ($file_info = getimagesize ($_FILES ['image_file']; $file_mime = $file_info ['mime'];)
  • Niveau 3: Lire les 100 premiers octets et de vérifier s'ils ont des octets dans la plage suivante: ASCII de 0 à 8, 12-31 (décimal).
  • Niveau 4: Vérifier les numéros de magie dans l'en-tête (première de 10 à 20 octets du fichier). Vous pouvez trouver certains des octets d'en-tête de fichiers à partir de ici.:

    http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Examples
  • vous pourriez vouloir exécuter" is_uploaded_file " sur les $_FILES['my_files']['tmp_name'] ainsi. Voir

    http://php.net/manual/en/function.is-uploaded-file.php

En voici une grande partie, mais ce n'est pas tout. (Si vous savez quelque chose de plus qui pourrait aider à rendre le téléchargement encore plus sûr, s'il vous plaît partager.)

C'EST CE QUE NOUS AVONS MAINTENANT

  • PHP Principale:

    function uploadFile ($file_field = null, $check_image = false, $random_name = false) {
    
    //Config Section    
    //Set file upload path
    $path = 'uploads/'; //with trailing slash
    //Set max file size in bytes
    $max_size = 1000000;
    //Set default file extension whitelist
    $whitelist_ext = array('jpeg','jpg','png','gif');
    //Set default file type whitelist
    $whitelist_type = array('image/jpeg', 'image/jpg', 'image/png','image/gif');
    
    //The Validation
    // Create an array to hold any output
    $out = array('error'=>null);
    
    if (!$file_field) {
      $out['error'][] = "Please specify a valid form field name";           
    }
    
    if (!$path) {
      $out['error'][] = "Please specify a valid upload path";               
    }
    
    if (count($out['error'])>0) {
      return $out;
    }
    
    //Make sure that there is a file
    if((!empty($_FILES[$file_field])) && ($_FILES[$file_field]['error'] == 0)) {
    
    // Get filename
    $file_info = pathinfo($_FILES[$file_field]['name']);
    $name = $file_info['filename'];
    $ext = $file_info['extension'];
    
    //Check file has the right extension           
    if (!in_array($ext, $whitelist_ext)) {
      $out['error'][] = "Invalid file Extension";
    }
    
    //Check that the file is of the right type
    if (!in_array($_FILES[$file_field]["type"], $whitelist_type)) {
      $out['error'][] = "Invalid file Type";
    }
    
    //Check that the file is not too big
    if ($_FILES[$file_field]["size"] > $max_size) {
      $out['error'][] = "File is too big";
    }
    
    //If $check image is set as true
    if ($check_image) {
      if (!getimagesize($_FILES[$file_field]['tmp_name'])) {
        $out['error'][] = "Uploaded file is not a valid image";
      }
    }
    
    //Create full filename including path
    if ($random_name) {
      // Generate random filename
      $tmp = str_replace(array('.',' '), array('',''), microtime());
    
      if (!$tmp || $tmp == '') {
        $out['error'][] = "File must have a name";
      }     
      $newname = $tmp.'.'.$ext;                                
    } else {
        $newname = $name.'.'.$ext;
    }
    
    //Check if file already exists on server
    if (file_exists($path.$newname)) {
      $out['error'][] = "A file with this name already exists";
    }
    
    if (count($out['error'])>0) {
      //The file has not correctly validated
      return $out;
    } 
    
    if (move_uploaded_file($_FILES[$file_field]['tmp_name'], $path.$newname)) {
      //Success
      $out['filepath'] = $path;
      $out['filename'] = $newname;
      return $out;
    } else {
      $out['error'][] = "Server Error!";
    }
    
     } else {
      $out['error'][] = "No file uploaded";
      return $out;
     }      
    }
    
    
    if (isset($_POST['submit'])) {
     $file = uploadFile('file', true, true);
     if (is_array($file['error'])) {
      $message = '';
      foreach ($file['error'] as $msg) {
      $message .= '<p>'.$msg.'</p>';    
     }
    } else {
     $message = "File uploaded successfully".$newname;
    }
     echo $message;
    }
    
  • et le formulaire:

    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data" name="form1" id="form1">
    <input name="file" type="file" id="imagee" />
    <input name="submit" type="submit" value="Upload" />
    </form>
    

donc, ce que je demande c'est d'aider en postant des extraits de codes qui m'aideront (et tout le monde) à faire ce Script de téléchargement D'Image pour rendre super sécurisé. Ou en partageant/créant un script complet avec tous les morceaux ajoutés.

28
demandé sur Community 2016-07-21 19:15:17

2 réponses

lorsque vous commencez à travailler sur un script de téléchargement d'image sécurisé, il y a beaucoup de choses à considérer. Je suis loin d'être un expert en la matière, mais on m'a demandé de développer ça une fois dans le passé. Je vais passer par tout le processus que j'ai traversé ici pour que tu puisses le suivre. Pour cela, je vais commencer avec un format html très basique et un script php qui gère les fichiers.

format HTML:

<form name="upload" action="upload.php" method="POST" enctype="multipart/form-data">
    Select image to upload: <input type="file" name="image">
    <input type="submit" name="upload" value="upload">
</form>

fichier PHP:

<?php
$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?> 

premier problème: types de fichiers

Les attaquants n'ont pas à utiliser le formulaire sur votre site web pour télécharger des fichiers sur votre serveur. Les demandes de courrier peuvent être interceptées de plusieurs façons. Pensez aux navigateurs addons, proxies, scripts Perl. Peu importe nos efforts, nous ne pouvons pas empêcher un attaquant d'essayer de télécharger quelque chose (s)qu'il n'est pas censé télécharger. Toute notre sécurité doit donc être assurée à serverside.

le premier problème: les types de fichiers. Dans le script ci-dessus, un attaquant peut télécharger tout ce qu'il veut, comme un script php par exemple, et suivre un lien direct pour l'exécuter. Donc pour éviter cela, nous implémentons vérification du type de contenu :

<?php
if($_FILES['image']['type'] != "image/png") {
    echo "Only PNG images are allowed!";
    exit;
}

$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?>

malheureusement, cela ne suffit pas. Comme je l'ai déjà mentionné, l'attaquant a le contrôle total sur la requête. Rien ne l'empêchera de modifier les en-têtes de requête et simplement de changer le contenu tapez "image / png". Ainsi, au lieu de compter seulement sur l'entête Content-type, il serait préférable aussi de valider le contenu du fichier téléchargé. Voici où la bibliothèque php GD est utile. En utilisant getimagesize() , nous traiterons l'image avec la bibliothèque GD. Si ce n'est pas une image, cela échouera et par conséquent le téléchargement entier échouera:

<?php
$verifyimg = getimagesize($_FILES['image']['tmp_name']);

if($verifyimg['mime'] != 'image/png') {
    echo "Only PNG images are allowed!";
    exit;
}

$uploaddir = 'uploads/';

$uploadfile = $uploaddir . basename($_FILES['image']['name']);

if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {
    echo "Image succesfully uploaded.";
} else {
    echo "Image uploading failed.";
}
?>

nous n'en sommes pas encore là. La plupart des types de fichiers d'image permettent d'ajouter des commentaires textuels. Encore une fois, rien empêche l'attaquant d'ajouter du code php comme commentaire. La bibliothèque GD évaluera ceci comme une image parfaitement valide. L'interpréteur PHP ignorerait complètement l'image et lancerait le code php dans le commentaire. Il est vrai que cela dépend de la configuration de php quelles extensions de fichiers sont traitées par l'interpréteur php et lesquelles ne le sont pas, mais étant donné qu'il y a de nombreux développeurs qui n'ont aucun contrôle sur cette configuration en raison de l'utilisation d'un VPS, nous ne pouvons pas supposer que l'interpréteur php ne le traitement de l'image. C'est pourquoi ajouter une extension de fichier liste blanche n'est pas assez sûr non plus.

la solution à cela serait de stocker les images dans un endroit où un attaquant ne peut pas accéder au fichier directement. Cela pourrait être en dehors de la racine du document ou dans un répertoire protégé par un .fichier htaccess:

order deny,allow
deny from all
allow from 127.0.0.1

modifier: après avoir parlé avec d'autres programmeurs PHP, je suggère fortement d'utiliser un dossier en dehors de la racine du document, parce que htaccess n'est pas toujours fiable.

nous avons toujours besoin de l'utilisateur ou de tout autre visiteur pour être en mesure de voir l'image cependant. Nous allons donc utiliser php pour récupérer l'image pour eux:

<?php
$uploaddir = 'uploads/';
$name = $_GET['name']; // Assuming the file name is in the URL for this example
readfile($uploaddir.$name);
?>

Deuxième problème: l'inclusion de fichier en Local attaques

Bien que notre script soit raisonnablement sécurisé Maintenant, nous ne pouvons pas supposer que le serveur ne souffre pas d'autres vulnérabilités. Une vulnérabilité de sécurité commune est appelée Local file inclusion . Pour expliquer cela, je dois ajouter un code d'exemple:

<?php
if(isset($_COOKIE['lang'])) {
   $lang = $_COOKIE['lang'];
} elseif (isset($_GET['lang'])) {
   $lang = $_GET['lang'];
} else {
   $lang = 'english';
}

include("language/$lang.php");
?>

dans cet exemple, nous parlons d'un site web multilingue. Le langage des sites n'est pas considéré comme une information à "haut risque". Nous essayons d'obtenir la langue préférée des visiteurs à travers un cookie ou une requête GET et d'inclure le fichier requis basé sur celui-ci. Maintenant, examinez ce qui se passera lorsque l'attaquant entrera l'url suivante:

www.example.com/index.php?lang=../uploads/my_evil_image.jpg

PHP va inclure le fichier téléchargé par l'attaquant court-circuitant le fait qu'il ne peut pas accéder au fichier directement et nous sommes de retour à la case départ.

La solution à ce problème est de s'assurer que l'utilisateur ne connaît pas le nom de fichier sur le serveur. Au lieu de cela, nous allons changer le nom du fichier et même l'extension en utilisant une base de données pour garder une trace de celui-ci:

CREATE TABLE `uploads` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(64) NOT NULL,
    `original_name` VARCHAR(64) NOT NULL,
    `mime_type` VARCHAR(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;



<?php

if(!empty($_POST['upload']) && !empty($_FILES['image']) && $_FILES['image']['error'] == 0)) {

    $uploaddir = 'uploads/';

    /* Generates random filename and extension */
    function tempnam_sfx($path, $suffix){
        do {
            $file = $path."/".mt_rand().$suffix;
            $fp = @fopen($file, 'x');
        }
        while(!$fp);

        fclose($fp);
        return $file;
    }

    /* Process image with GD library */
    $verifyimg = getimagesize($_FILES['image']['tmp_name']);

    /* Make sure the MIME type is an image */
    $pattern = "#^(image/)[^\s\n<]+$#i";

    if(!preg_match($pattern, $verifyimg['mime']){
        die("Only image files are allowed!");
    }

    /* Rename both the image and the extension */
    $uploadfile = tempnam_sfx($uploaddir, ".tmp");

    /* Upload the file to a secure directory with the new name and extension */
    if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) {

        /* Setup a database connection with PDO */
        $dbhost = "localhost";
        $dbuser = "";
        $dbpass = "";
        $dbname = "";

        // Set DSN
        $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname;

        // Set options
        $options = array(
            PDO::ATTR_PERSISTENT    => true,
            PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
        );

        try {
            $db = new PDO($dsn, $dbuser, $dbpass, $options);
        }
        catch(PDOException $e){
            die("Error!: " . $e->getMessage());
        }

        /* Setup query */
        $query = 'INSERT INTO uploads (name, original_name, mime_type) VALUES (:name, :oriname, :mime)';

        /* Prepare query */
        $db->prepare($query);

        /* Bind parameters */
        $db->bindParam(':name', basename($uploadfile));
        $db->bindParam(':oriname', basename($_FILES['image']['name']));
        $db->bindParam(':mime', $_FILES['image']['type']);

        /* Execute query */
        try {
            $db->execute();
        }
        catch(PDOException $e){
            // Remove the uploaded file
            unlink($uploadfile);

            die("Error!: " . $e->getMessage());
        }
    } else {
        die("Image upload failed!");
    }
}
?>

donc maintenant nous avons fait ce qui suit:

  • nous avons créé un endroit sûr pour sauvegarder les images
  • nous avons traité l'image avec la bibliothèque GD
  • nous avons vérifié le type D'image MIME
  • nous avons renommé le nom du fichier et changé l'extension
  • nous avons sauvé à la fois le nouveau et l'original nom du fichier dans notre base de données
  • nous avons aussi enregistré le type MIME dans notre base de données

nous avons encore besoin de pouvoir afficher l'image aux visiteurs. Nous utilisons simplement la colonne id de notre base de données pour faire ceci:

<?php

$uploaddir = 'uploads/';
$id = 1;

/* Setup a database connection with PDO */
$dbhost = "localhost";
$dbuser = "";
$dbpass = "";
$dbname = "";

// Set DSN
$dsn = 'mysql:host='.$dbhost.';dbname='.$dbname;

// Set options
$options = array(
    PDO::ATTR_PERSISTENT    => true,
    PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
);

try {
    $db = new PDO($dsn, $dbuser, $dbpass, $options);
}
catch(PDOException $e){
    die("Error!: " . $e->getMessage());
}

/* Setup query */
$query = 'SELECT name, original_name, mime_type FROM uploads WHERE id=:id';

/* Prepare query */
$db->prepare($query);

/* Bind parameters */
$db->bindParam(':id', $id);

/* Execute query */
try {
    $db->execute();
    $result = $db->fetch(PDO::FETCH_ASSOC);
}
catch(PDOException $e){
    die("Error!: " . $e->getMessage());
}

/* Get the original filename */
$newfile = $result['original_name'];

/* Send headers and file to visitor */
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename='.basename($newfile));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($uploaddir.$result['name']));
header("Content-Type: " . $result['mime_type']);
readfile($uploaddir.$result['name']);
?>

grâce à ce script, le visiteur pourra visualiser l'image ou la télécharger avec son nom de fichier original. Cependant, (s)il ne peut pas accéder directement au fichier sur votre serveur et ne pourra pas non plus tromper votre serveur d'accès au fichier pour lui/elle (s)il n'a aucun moyen de savoir quel fichier il est. (S) il ne peut pas forcer votre répertoire de téléchargement non plus car il ne permet tout simplement pas à quiconque d'accéder au répertoire à l'exception du serveur lui-même.

et qui conclut mon script de téléchargement d'image sécurisé.

je voudrais ajouter que je n'ai pas inclus une taille maximale de fichier dans ce script, mais vous devriez facilement pouvoir le faire vous-même.

ImageUpload Classe

En raison de la forte demande de ce script, j'ai écrit une classe ImageUpload qui devrait rendre beaucoup plus facile pour vous tous de gérer en toute sécurité les images téléchargées par les visiteurs de votre site web. La classe peut gérer à la fois des fichiers simples et multiples à la fois, et vous fournit des fonctionnalités supplémentaires comme l'affichage, le téléchargement et la suppression d'images.



Puisque le code est simplement trop grand pour poster ici, vous pouvez télécharger la classe de MEGA ici:

Télécharger ImageUpload Classe

viens de lire le README.txt et suivez les instructions.

Going Open Source

Le projet Image Secure class est maintenant disponible sur mon profil GitHub . Ce, afin que les autres (vous?) peut contribuer au projet et faire une grande bibliothèque pour tout le monde. (actuellement buggé. Veuillez utiliser le haut de téléchargement jusqu'fixe).

57
répondu icecub 2018-05-16 21:07:29

bien, télécharger des fichiers en PHP est trop facile et sécurisé. Je recommande lear about:

pour télécharger un fichier en PHP vous avez deux méthodes, PUT et POST(peut-être plus..). Pour utiliser la méthode POST avec HTML need enable enctype sur le formulaire comme ceci:

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

puis, dans votre PHP, vous devez télécharger le fichier avec $_FILES comme ceci:

$_FILES['file']

puis vous devez déplacer de temp ("upload") avec move_uploaded_file:

if (move_uploaded_file($_FILES['file']['tmp_name'], YOU_PATH)) {
   // ...
}

et après le téléchargement du fichier, vous devez vérifier l'extension et la meilleure et la meilleure façon est d'utiliser pathinfo comme ceci:

$extension = pathinfo($_FILES['file']['tmp_name'],PATHINFO_EXTENSION);

Mais l'extension n'est pas sûr, car, vous pouvez télécharger le fichier avec l'extension .jpg mais avec mimetype text/php et c'est une porte dérobée. Je recommande donc de vérifier Real mimetype avec finfo_open comme ceci:

$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['file']['tmp_name']);

et, ne pas utiliser $_FILES['file']['type'] parce que parfois et en fonction du navigateur et de l'OS client, vous pouvez recevoir application/octet-stream , et ce mimetype no est un vrai le type mime de vous fichier téléchargé.

je pense qu'avec ceci vous pouvez télécharger le fichier avec la sécurité.

Désolé mon anglais, bye!

2
répondu Olaf Erlandsen 2016-07-29 12:12:00