Script de Carga de Imágenes Seguro Completo


No se si esto va a suceder, pero lo intentaré.

Durante la última hora hice una investigación sobre la seguridad de la carga de imágenes. Aprendí que hay muchas funciones para probar la carga.

En mi proyecto, necesito estar seguro con las imágenes subidas. También puede haber una gran cantidad de ella y puede requerir una gran cantidad de ancho de banda, por lo que comprar una API no es una opción.

Así que decidí obtener un script PHP completo para una carga de imágenes REALMENTE segura. También creo que ayudará a muchos de la gente por ahí, porque es imposible encontrar uno realmente seguro. Pero no soy experto en php, por lo que es realmente un dolor de cabeza para mí agregar algunas funciones, por lo que pediré ayuda a esta comunidad para crear un script completo de carga de imágenes REALMENTE seguro.

Realmente grandes temas sobre eso están aquí (sin embargo, solo están diciendo lo que se necesita para hacer el truco, pero no cómo hacer esto, y como dije no soy un maestro en PHP, por lo que no soy capaz de hacer todo esto por mí mismo): Carga de imágenes PHP lista de comprobación de seguridad https://security.stackexchange.com/questions/32852/risks-of-a-php-image-upload-form

En resumen, dicen que esto es lo que se necesita para cargar imágenes de seguridad (citaré las páginas anteriores):

  • Deshabilita PHP para que no se ejecute dentro de la carpeta upload usando.httaccess.
  • No permita la carga si el nombre del archivo contiene la cadena "php".
  • Solo permite extensiones: jpg,jpeg,gif y png.
  • Permitir solo el tipo de archivo de imagen.
  • No permitir imagen con dos tipos de archivo.
  • Cambie el nombre de la imagen. Subir a un subdirectorio no directorio raíz.

También:

  • Vuelva a procesar la imagen usando GD (o Imagick) y guarde la imagen procesada. Todos los demás son solo diversión aburrido para los hackers "
  • Como rr señaló, use move_uploaded_file () para cualquier carga "
  • Por cierto, te gustaría ser muy restrictivo sobre tu carga carpeta. Esos lugares son uno de los rincones oscuros donde muchas hazañas
    suceder. Esto es válido para cualquier tipo de carga y cualquier programación
    idioma/servidor. Comprobar
    https://www.owasp.org/index.php/Unrestricted_File_Upload
  • Nivel 1: Compruebe la extensión (el archivo de extensión termina con)
  • Nivel 2: Compruebe el tipo MIME ($file_info = getimagesize($_FILES['archivo_imagen']; $file_mime = $file_info['mime'];)
  • Nivel 3: Leer los primeros 100 bytes y compruebe si tienen algún byte en el siguiente rango: ASCII 0-8, 12-31 (decimal).
  • Nivel 4: Compruebe si hay números mágicos en el encabezado (primeros 10-20 bytes del archivo). Usted puede encontrar algunos de los archivos de cabecera bytes de aquí:
    http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Examples
  • es posible Que desee ejecutar "is_uploaded_file" en el $_FILES['my_files']['tmp_name'] así. Véase
    http://php.net/manual/en/function.is-uploaded-file.php

Aquí hay una gran parte de ella, pero aún así eso no es todo. (Si sabes algo más que podría ayudar a hacer que la carga sea aún más segura, por favor comparte.)

ESTO ES LO QUE TENEMOS AHORA

  • PHP principal:

    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;
    }
    
  • Y la forma:

    <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>
    

Por lo tanto, lo que estoy pidiendo es ayudar mediante la publicación de fragmentos de códigos que me ayudarán (y todos los demás) para hacer que este script de carga de imágenes sea súper seguro. O compartiendo / creando un script completo con todos los fragmentos añadidos.

Author: Community, 2016-07-21

2 answers

Cuando empiezas a trabajar en un script seguro de subida de imágenes, hay muchas cosas que considerar. Ahora no soy ni de lejos un experto en esto, pero me han pedido desarrollar esto una vez en el pasado. Voy a recorrer todo el proceso por el que he pasado aquí para que puedas seguirme. Para esto voy a empezar con un formulario html muy básico y script php que maneja los archivos.

Formulario 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>

Archivo 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.";
}
?> 

Primer problema: Archivo tipos
Los atacantes no tienen que usar el formulario en su sitio web para cargar archivos en su servidor. Las solicitudes POST pueden ser interceptadas de varias maneras. Piense en complementos de navegador, proxies, scripts de Perl. No importa lo mucho que lo intentemos, no podemos evitar que un atacante intente cargar algo (s)que no se supone que debe. Así que toda nuestra seguridad tiene que hacerse en el servidor.

El primer problema son los tipos de archivo. En el script anterior, un atacante podría subir cualquier cosa que desee, como un php script por ejemplo, y siga un enlace directo para ejecutarlo. Así que para evitar esto, implementamos Verificación de tipo de contenido :

<?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.";
}
?>

Desafortunadamente esto no es suficiente. Como mencioné antes, el atacante tiene control total sobre la solicitud. Nada le impedirá modificar los encabezados de solicitud y simplemente cambiar el tipo de Contenido a "image / png". Así que en lugar de solo confiar en el encabezado Content-type, sería mejor validar también el contenido del archivo cargado. Aquí es donde la biblioteca php GD es útil. Usando getimagesize(), estaremos procesando la imagen con la biblioteca GD. Si no es una imagen, esto fallará y por lo tanto toda la carga fallará:

<?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.";
}
?>

Todavía no hemos llegado a eso. La mayoría de los tipos de archivos de imagen permiten comentarios de texto añadidos a ellos. De nuevo, nada impide que el atacante añada algún código php como comentario. La biblioteca GD evaluará esto como una imagen perfectamente válida. El intérprete PHP ignoraría completamente la imagen y ejecute el código php en el comentario. Es cierto que depende de la configuración de php qué extensiones de archivo son procesadas por el intérprete php y cuáles no, pero como hay muchos desarrolladores por ahí que no tienen control sobre esta configuración debido al uso de un VPS, no podemos asumir que el intérprete php no procesará la imagen. Esta es la razón por la que agregar una lista blanca de extensión de archivo tampoco es lo suficientemente seguro.

La solución a esto sería almacenar las imágenes en una ubicación donde un el atacante no puede acceder al archivo directamente. Esto podría estar fuera de la raíz del documento o en un directorio protegido por a .archivo htaccess:

order deny,allow
deny from all
allow from 127.0.0.1

Edit: Después de hablar con otros programadores PHP, recomiendo encarecidamente usar una carpeta fuera de la raíz del documento, porque htaccess no siempre es confiable.

Sin embargo, todavía necesitamos que el usuario o cualquier otro visitante pueda ver la imagen. Así que usaremos php para recuperar la imagen para ellos:

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

Segundo problema: Local ataques de inclusión de archivos
Aunque nuestro script es razonablemente seguro por ahora, no podemos asumir que el servidor no sufre de otras vulnerabilidades. Una vulnerabilidad de seguridad común se conoce como Inclusión de archivos locales. Para explicar esto, necesito añadir un código de ejemplo:

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

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

En este ejemplo estamos hablando de un sitio web en varios idiomas. El lenguaje de los sitios no es algo considerado como información de "alto riesgo". Tratamos de conseguir el idioma preferido de los visitantes a través de un cookie o una solicitud GET e incluir el archivo requerido basado en ella. Ahora considere lo que sucederá cuando el atacante ingrese la siguiente url:

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

PHP incluirá el archivo cargado por el atacante evitando el hecho de que no puede acceder al archivo directamente y estamos de vuelta en el punto de partida.

La solución a este problema es asegurarse de que el usuario no conoce el nombre del archivo en el servidor. En su lugar, cambie el nombre del archivo e incluso la extensión usando una base de datos para realizar un seguimiento de él:

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!");
    }
}
?>

Así que ahora hemos hecho lo siguiente: {[12]]}

  • Hemos creado un lugar seguro para guardar las imágenes
  • Hemos procesado la imagen con la biblioteca GD
  • Hemos comprobado el tipo MIME de la imagen
  • Hemos cambiado el nombre del archivo y la extensión
  • Hemos guardado tanto el nombre de archivo nuevo como el original en nuestra base de datos
  • , también Hemos guardado el tipo MIME en nuestra base de datos

Todavía necesitamos poder mostrar la imagen a los visitantes. Simplemente usamos la columna id de nuestra base de datos para hacer esto:

<?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']);
?>

Gracias a este script el visitante podrá ver la imagen o descargarla con su nombre de archivo original. Sin embargo, (s)no puede acceder al archivo en su servidor directamente ni podrá engañar a su servidor para acceder al archivo por él/ella, ya que no tiene forma de saber qué archivo es. (S) no puede brute forzar su directorio de carga ya sea, ya que simplemente no permite que nadie acceda al directorio, excepto el propio servidor.

Y eso concluye mi script seguro de subida de imágenes.

Me gustaría añadir que no incluí un tamaño máximo de archivo en este script, pero debería poder hacerlo usted mismo fácilmente.

ImageUpload Class
Debido a la alta demanda de este script, he escrito una clase ImageUpload que debería hacer que sea mucho más fácil para todos ustedes maneje de forma segura las imágenes subidas por los visitantes de su sitio web. La clase puede manejar archivos individuales y múltiples a la vez, y le proporciona características adicionales como mostrar, descargar y eliminar imágenes.

Dado que el código es simplemente grande para publicar aquí, puede descargar la clase de MEGA aquí:

Descargar ImageUpload Class

Acaba de leer el README.txt y siga las instrucciones.

Pasando al Código Abierto
La Imagen Segura el proyecto de clase ahora también está disponible en mi perfil Github . Esto para que otros (usted?) puede contribuir al proyecto y hacer de esta una gran biblioteca para todos. (actualmente con micrófonos ocultos. Utilice por favor la transferencia directa antedicha until fijo).

 57
Author: icecub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-16 21:07:29

Bueno, subir archivos en PHP es demasiado fácil y seguro. Recomiendo leer sobre:

Para subir un archivo en PHP tienes dos métodos, PUT y POST(quizás más..). Para usar el método POST con HTML necesita habilite escriba en un FORMULARIO como este:

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

Entonces, en tu PHP necesitas coger que subes el archivo con _ _FILES así:

$_FILES['file']

Entonces necesitas move from temp ("upload") con move_uploaded_file:

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

Y después de cargar el archivo, necesita verificar la extensión y la mejor y mejor manera es usar pathinfo así:

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

Pero la extensión no es segura porque, puede cargar el archivo con la extensión .jpg pero con mimetype text/php y esto es un trasera. Por lo tanto, recomiendo comprobar el tipo mime real con finfo_open así:

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

Y, no use $_FILES['file']['type'] porque a veces y dependiendo del navegador y del sistema operativo del cliente, puede recibir application/octet-stream, y este tipo mime no es el real tipo mime del archivo cargado.

Creo que con esto se puede subir el archivo con seguridad.

Lo siento mi inglés, adiós!

 2
Author: Olaf Erlandsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-29 12:12:00