Seguridad en Upload de Archivos

En un oportunidad hemos explicado como hacer Upload de Archivos con PHP, donde explicamos el proceso de subir el archivo y obviamos el tema de la seguridad, esto para ser didáctico. Pero la seguridad es muy importante, por ejemplo alguien puede subir un archivo PHP con contenga sentencias para crear, editar, eliminar archivos y carpetas con lo cual podrían tomar el control de nuestro hosting, entonces es el momento de asegurar el proceso de subir archivos.

Seguridad en Upload de Archivos
Lo primero es evitar que el usuario pueda subir cualquier archivo para ello se pueden seguir las siguientes reglas:

  • El archivo subido nunca debe ser accesible inmediatamente por el cliente.
  • Generar nombres aleatorios a los archivos subidos.
  • Filtrar los archivos para no permitir archivos PHP.
  • Subir los archivos a carpetas fuera del directorio de publicación.
  • Utilizar is_uploaded_file para verificar que el archivo se ha subido.
  • Utilizar move_uploaded_file para copiar el archivo al directorio final.

Seguridad en Upload de Imágenes
En el caso de imágenes, estas generalmente deben ser publicador inmediatamente, entonces hay que hacer varias verificaciones adicionales:

  • Verificar el Content-Type del archivo para asegurarse que el archivo es una imagen.
  • Verificar si el archivo es imagen con la función getimagesize
  • Limitar el archivo a un máximo de tamaño.

Implementado Clase PHP para Upload
Siguiendo las reglas que hemos mencionado hemos creado una pequeña clase que facilita el subir archivos e incluye la verificación por tipo de archivo, extensión, tamaño de archivo. Entonces creamos un archivo llamado upload.php donde incluimos el siguiente código:

// file: upload.php
// author: http://blog.unijimpe.net
// date: 27/02/2010
class Upload {
	var $maxsize = 0;
	var $message = "";
	var $newfile = "";
	var $newpath = "";
	
	var $filesize = 0;
	var $filetype = "";
	var $filename = "";
	var $filetemp;
	var $fileexte;
	
	var $allowed;
	var $blocked;
	var $isimage;
	var $isupload;
	
	function Upload() {
		$this->allowed = array("image/bmp","image/gif","image/jpeg","image/pjpeg","image/png","image/x-png");
		$this->blocked = array("php","phtml","php3","php4","js","shtml","pl","py");
		$this->message = "";
		$this->isupload = false;
	}
	function setFile($field) {
		$this->filesize = $_FILES[$field]['size'];
		$this->filename = $_FILES[$field]['name'];
		$this->filetemp = $_FILES[$field]['tmp_name'];
		$this->filetype = mime_content_type($this->filetemp);
		$this->fileexte = substr($this->filename, strrpos($this->filename, '.')+1);
		
		$this->newfile = substr(md5(uniqid(rand())),0,8).".".$this->fileexte;
	}
	function setPath($value) {
		$this->newpath = $value;
	}
	function setMaxSize($value) {
		$this->maxsize = $value;	
	}
	function isImage($value) {
		$this->isimage = $value;
	}
	function save() {
		if (is_uploaded_file($this->filetemp)) {
			// check if file valid
			if ($this->filename == "") {
				$this->message = "No file upload";
				$this->isupload = false;
				return false;
			}
			// check max size
			if ($this->maxsize != 0) {
				if ($this->filesize > $this->maxsize*1024) {
					$this->message = "Large File Size";
					$this->isupload = false;
					return false;
				}
			}
			// check if image
			if ($this->isimage) {
				// check dimensions
				if (!getimagesize($this->filetemp)) {
					$this->message = "Invalid Image File";
					$this->isupload = false;
					return false;	
				}
				// check content type
				if (!in_array($this->filetype, $this->allowed)) {
					$this->message = "Invalid Content Type";
					$this->isupload = false;
					return false;
				}
			}
			// check if file is allowed
			if (in_array($this->fileexte, $this->blocked)) {
				$this->message = "File Not Allowed - ".$this->fileexte;
				$this->isupload = false;
				return false;
			}
					
			if (move_uploaded_file($this->filetemp, $this->newpath."/".$this->newfile)) {
				$this->message = "File succesfully uploaded!";
				$this->isupload = true;
				return true;
			} else {
				$this->message = "File was not uploaded please try again";
				$this->isupload = false;
				return false;
			}
		} else {
			$this->message = "File was not uploaded please try again";
			$this->isupload = false;
			return false;
		}
	}	
}

Esta clase es sencilla de utilizar y como podemos ver tiene los siguientes métodos:

  • setFile: nombre del campo del formulario que deseamos subir.
  • setPath: carpeta donde se guardará el archivo,
  • setMaxSize: permite definir el tamaño máximo del archivo (opcional).
  • isImage: permite definir si se hará verificación de imágenes.
  • save: guarda el archivo en la carpeta de destino.

Adicionalmente tiene dos propiedades muy útiles:

  • isupload: devuelve verdadero o falso indicando si el archivo se ha subido.
  • message: devuelve un mensaje indicando que ocurrió en el proceso.

Ejemplo de Uso
Primero creamos un formulario con un campo llamado «archivo» y adicionalmente un campo oculto que lo utilizaremos para verificar que el formulario se ha enviado.

<form method="post" enctype="multipart/form-data">
      <input name="archivo" type="file" id="archivo" />
      <input name="enviar" type="submit" id="enviar" value="Upload File" />
      <input name="action" type="hidden" value="upload" />
</form>

El siguiente paso es incluir el archivo al inicio de nuestro script, luego creamos una instancia de la clase, definimos la carpeta donde se guardarán los archivos, el nombre del campo y procedemos al upload del archivo.

require("upload.php");
if ($_POST["action"] == "upload") {
	$fupload = new Upload();
	$fupload->setPath("files");
	$fupload->setFile("archivo");
	$fupload->save();
	echo $fupload->message;
}

Si deseamos subir imágenes para posteriormente crear un thumbnail, el código sería de la siguiente forma:

require("upload.php");
if ($_POST["action"] == "upload") {
	$fupload = new Upload();
	$fupload->setPath("files");
	$fupload->setFile("archivo");
	$fupload->isImage(true);
	$fupload->save();
	if ($fupload->isupload) {
		// generate thumbnail
	} else {
		// show error message
		echo $fupload->message;
	}
}

Consideraciones Adicionales
Esta clase implementa algunos puntos para asegurar nuestros scripts, pero no es completamente seguro ya que hay múltiples técnicas para saltar estas barreras como lo explican en Secure file upload in PHP web applications. Si tienen sugerencias o mejoras al script sería de gran ayuda para todos. Les dejo el archivo fuente para que puedan realizar sus pruebas y/o correcciones.

[download id=»34″ autop=»false»]

Nota Adicional
En estos dias ha circulado el documento (PHP upload – (unijimpe) Remote File Upload Vulnerability ) que explica como violentar los scripts creados con el ejemplo del post Upload de Archivos con PHP, para ello hay que seguir las siguiente sugerencias para protegerse:

  • Cambiar el titulo de la página «PHP upload – unijimpe», los atacantes hacen una busqueda de esta palabra y pueden ubicar las páginas que utilizan este script el cual puede ser atacado. Entonces si vas a utilizar el script de upload por lo menos hay que cambiar el título al ejemplo descargado.
  • Otra sugerencia es no mostrar el archivo recién subido, el administrador debe revisarlo antes que sea publicado.
  • Agregar la validación por content-type, por la extensión del archivo como hemos mencionado en este post.
  • Filtrar los archivos y solo permitir los archivos necesarios.

Mas Información

Comentarios Total 28 comentarios

Upload de Archivos con PHP | unijimpe
Publicado: 04/03/2010 1:02 am

[…] Muchas veces necesitamos hacer uploads de archivos en nuestros proyectos para muchos es algo sencillo pero para los que recién empiezan les explicare como se realiza el proceso. Este upload lo haremos utilizando php. Este artículo explica el proceso de subir archivos con PHP. Para consideraciones de seguridad cuando se suben archivos pueden leer Seguridad en Upload de Archivos. […]

spertegaz
Publicado: 04/03/2010 3:45 am

Una programación muy limpia y clara. Me bajo la clase…

Alberto
Publicado: 04/03/2010 7:01 pm

La obtención de la extensión sepuede hacer mejor de esta manera:

$s_ext = pathinfo($_FILES['myfile']['name'], PATHINFO_EXTENSION);

Es mucho más rápido y seguro :D

Lucas
Publicado: 06/03/2010 9:31 am

Hola! queria saber si ustedes me pueden ayudar! COmo podria hacer para que al subir la imagen, se le asigne un nombre aleatorio o no, pero ademas que contenga el nombre y apellido de la persona que lo sube.

Yo pido Nombre y Apellido en un formulario, y ademas subir la imagen, entonces necesitara que los campos t_nombre y t_apellido se asignen al nombre del upload…

En una foto que se llama foto.jpg se sube asi:
Ej: nombre_apellido_foto.jpg

Se podra????????????

Muchisimas graciass!!!

Carluis Perez
Publicado: 12/03/2010 1:43 pm

PERFECTO!!! unijimpe! de esto era lo que hablaba en el tema de Upload de Archivos con PHP esta muy bueno y es grandioso que expongas este tema,ya que ahora los propietarios de websitios puedes usar el Upload de Archivos con PHP libremente! FELICIDADES…

El preguntador
Publicado: 13/03/2010 10:19 pm

Fatal error: Cannot redeclare Upload::$message in /home/xxxxxxx/public_html/upload-php/upload.php on line 22

jacin
Publicado: 15/03/2010 8:05 am

Genial!!! Me ha venido de maravilla. Gracias.

jacin
Publicado: 15/03/2010 8:40 am

A mi tambien me da el error: Fatal error: Cannot redeclare Upload::$message in /xxx/public_html/lib/php/upload.php on line 22

unijimpe
Publicado: 15/03/2010 11:17 pm

Gracias por los comentarios, yo había desarrollado la clase sobre un servidor PHP4 y no obtuve errores. Hoy probé la clase en un servidor PHP5 y me mostró el mensaje:

Fatal error: Cannot redeclare Upload::$message in /public_html/lib/php/upload.php on line 22

El mensaje indica que se había declarado dos veces la variable $message, entonces eliminé la línea 22 con lo cual se ha corregido el problema que mencionan.

He corregido el código fuente publicado y el archivo para la descarga.

rio-abajo
Publicado: 20/03/2010 6:06 pm

El enlace al documento PDF está mal… (en la sección Más Información de este post)

Mario
Publicado: 04/04/2010 5:19 pm

Muchas gracias por la clase y el aviso, funciona perfectamente. Por cierto ¿podría ser una solución subir los archivos a un nivel o carpeta inaccesible? Tengo apuntado el dominio a una carpeta que está debajo del nivel donde guardo los logs, cgi y demás por lo que no pueden acceder, por lo menos de manera convencional, a la carpeta donde alojo los archivos. Para descargarlos o visualizarlos también tengo un script intermedio para que no se vean las rutas, pero ¿pensáis que es suficiente?

Un saludo.

Dante
Publicado: 14/07/2010 1:02 pm

Fatal error: Call to undefined function mime_content_type() in …/upload.php on line 33

unijimpe
Publicado: 14/07/2010 11:02 pm

Dante el mensaje indica que la versión de PHP que estas utilizando no tiene la función mime_content_type. Esta función esta disponible a partir de PHP 4.3. Mas información en http://php.net/manual/es/function.mime-content-type.php

Dante
Publicado: 15/07/2010 2:10 pm

Hola unijimpe me parece muy buena tu clase felicitaciones!, la version que tengo es PHP Version 5.2.8, al parecer la funcion mime_content_type ha sido declarada obsoleta para poder activarla debemos editar el php.ini, y dan como alternativa la extensión PECL «Fileinfo» que se dice es más completa (no he intentado).
He reemplazado la linea para obtener el type por la siguiente:
$this->filetype = $_FILES[$field]['type'];
Hasta que encuentre otra solución mas viable, de todas maneras la funcion getimagesize da mucha seguridad al upload.

Saludos

jorge
Publicado: 24/08/2010 10:35 am

tuve un error en la funcion «setfile» en la linea:
$this->filetype = mime_content_type($this->filetemp);

pero se soluciono con:
$this->filetype = $_FILES[$field][‘type’];

el resto corre de maravilla, gracias

pedro
Publicado: 09/02/2011 3:27 pm

He visto el tutorial y me parece genial, aunque no se como incorporar un campo de tipo textarea. En el formulario no hay problema. Lo que se me complica es el tratamiento en php. puesto que he visto que solo deja subir extensiones de imagenes, y bloquea php, js,shtml, etc…

De todas formas muchas gracias por todo lo que ayudais

pedro
Publicado: 11/02/2011 3:17 pm

Lo he provado y va genial todo soys unos cracks.
Solo me queda una duda,
¿Hay alguna manera de que en vez de var la imagen en otra pagina, se pudiese ver en al index al pinchar el link de la llista de achivos subidos?

max
Publicado: 15/03/2011 9:39 pm

encontre esto por la red

http://www.docstoc.com/docs/39894379/PHP-upload-unijimpe-Remote-File-Upload-Vulnerability

Espero sirba …

Elias
Publicado: 12/07/2011 5:03 pm

Grosso tío, todo un capo. El mejor blog de php que conozco.

Jaime Eduardo
Publicado: 15/12/2011 10:48 pm

Ayudaaaaaaaaaaaaaaaaa !!

Bueno de antemano gracias por tan excelente codigo !. :D

Le he hecho unas ampliaciones para una aplicacion de administrador de articulos, ahora poseo un problema grande, y de verdad que me la he pasado literalmente 1 dia entero buscando la manera que funcione pero nada :( Agradeceria infinitamente esta ayuda (incluyendo intercambios de links y mencion en facebook :D ).

Ya tengo este codigo funcionando (sube y guarda los archivos), ahora lo puse para que me guardara la ruta donde queda el archivo en un campo del registro de la tabla productos en una base de datos MySQL. Hasta ahi todo muy bien, ya hasta al listado le agregue el boton eliminar registro y funciona, le agregué una paginacion y todo va bn, ahora le agregue la funcion de modificar/actualizar, y todo iba muy bn pero cuando reviso campo por campo, oh sorpresa !, cuando en un campo file no selecciono algun archivo, al actualizar me guarda en la base de datos un valor vacio (creo que es obvio), necesito es evitar esto !; es decir y creo que me falto aclarar, adicional a todo esto, logré que funcionara este script para subir 4 archivos diferentes dentro de un mismo formulario. Entonces, cuando agrego el archivo no 1, este queda guardado y el resto queda en blanco, pero cuando quiero actualizar SOLO el no 2, el no 1 y el resto quedan en blanco.

Porfa AUXILIOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO se los agradezco mucho cualquier colaboracion !.

– He intentado con if(empy… comprobando que file no este vacio, he probado con funciones case en php, he probado muchas cosas pero nada :(

Graciassssss

unijimpe
Publicado: 16/12/2011 2:33 pm

Lo que tienes que hacer es primero en tu formulario poner los nombres de los archivos actuales en campos ocultos. Luego en el PHP que procesa el upload, verifica que el nombre del archivo subido sea diferente de vacio, si es vacio utilizar el nombre del campo oculto y listo.

$file1 = $fupload->newfile;
if ($fupload->newfile != "") {
$file1 = $_POST["file1-hidden"];
}

luis
Publicado: 17/02/2012 4:27 pm

Estimado
Llegué hasta aquí por simple curiosidad, ya que no soy programador PHP
Pero luego de leer este y otros post y sus comentarios no quería dejar pasar la oportunidad de felicitarlo, no por su calidad de programador que no estoy en condiciones de evaluar, sino por su calidad de persona.
Su dedicación, tanto a la hora de escribir como a la hora de responder a las necesidades de sus lectores, es realmente encomiable.
Saludos y felicitaciones.


[…] Este artículo explica solo el proceso de subir archivos con PHP. Para consideraciones de seguridad pueden leer Seguridad en Upload de Archivos. […]

Pablo
Publicado: 27/05/2012 10:03 pm

Hola muy bueno el tuto, pero como puedo obtener el nombre del archivo que subo, para poder cargarlo en una base de datos?

Muchas gracias!

Juan
Publicado: 30/05/2012 1:41 am

Hola, la verdad que está muy bueno el código, por lo que te doy las gracias. Ahora tengo una duda y es como podria hacer para subir más de un archivo a la vez, por ejemplo un limite de 6 archivos simultaneos, pero que si algún campo queda vacio no indique como que no se ha cargado una imágen?

Muchas gracias!!!

david
Publicado: 15/04/2013 10:58 pm

con que variable la guardo en la base de datos

daniel
Publicado: 25/04/2013 2:06 pm

como lo podemos ser autoadaptble en el caso en que se puedan subir cualuiertipo de archivos. es decir tanto imagen como textos, u otro archivos desconocidos?

Cesar Fimbres
Publicado: 23/09/2013 3:03 am

Agregue la siguiente función para reemplazar la linea 31 » $this->filetype = mime_content_type($this->filetemp);», ya que me generaba error

function returnMIMEType($filename)
{
preg_match("|\.([a-z0-9]{2,4})$|i", $filename, $fileSuffix);

switch(strtolower($fileSuffix[1]))
{
case "js" :
return "application/x-javascript";

case "json" :
return "application/json";

case "jpg" :
case "jpeg" :
case "jpe" :
return "image/jpg";

case "png" :
case "gif" :
case "bmp" :
case "tiff" :
return "image/".strtolower($fileSuffix[1]);

case "css" :
return "text/css";

case "xml" :
return "application/xml";

case "doc" :
case "docx" :
return "application/msword";

case "xls" :
case "xlt" :
case "xlm" :
case "xld" :
case "xla" :
case "xlc" :
case "xlw" :
case "xll" :
return "application/vnd.ms-excel";

case "ppt" :
case "pps" :
return "application/vnd.ms-powerpoint";

case "rtf" :
return "application/rtf";

case "pdf" :
return "application/pdf";

case "html" :
case "htm" :
case "php" :
return "text/html";

case "txt" :
return "text/plain";

case "mpeg" :
case "mpg" :
case "mpe" :
return "video/mpeg";

case "mp3" :
return "audio/mpeg3";

case "wav" :
return "audio/wav";

case "aiff" :
case "aif" :
return "audio/aiff";

case "avi" :
return "video/msvideo";

case "wmv" :
return "video/x-ms-wmv";

case "mov" :
return "video/quicktime";

case "zip" :
return "application/zip";

case "tar" :
return "application/x-tar";

case "swf" :
return "application/x-shockwave-flash";

default :
if(function_exists("mime_content_type"))
{
$fileSuffix = mime_content_type($filename);
}

return "unknown/" . trim($fileSuffix[0], ".");
}
}

 

Comentar

En este blog los comentarios están moderados, serán mostrados cuando el administrador los apruebe. Por favor, evita comentarios ofensivos u obscenos por que no serán aprobados.
Si deseas publicar código fuente debes hacerlo entre las etiquedas <code> y </code>, además debes reemplazar los carácteres < por &lt; y > por &gt;.

(Requerido)

(Requerido, no será publicado)

(Requerido)

(Tags aceptados: <a> <em> <strong> <code> <ul> <li>)