PHP

PHP

Tutorial PHP: Manejo de archivos en formularios

PHP: Aprende a configurar php.ini y seguir prácticas de seguridad al subir archivos. Guía completa para asegurar tus formularios.

Aprende PHP GRATIS y certifícate

Configuración necesaria (php.ini: file_uploads, upload_max_filesize, etc.)

Para permitir subir archivos mediante formularios en PHP, es imprescindible ajustar ciertas directivas en el archivo de configuración php.ini. Este archivo define cómo se comporta PHP en diversos aspectos, incluyendo el manejo de uploads.

Por defecto, la directiva file_uploads está habilitada, permitiendo la subida de archivos. Sin embargo, es importante verificar que esté establecida en On:

file_uploads = On

Otra directiva crucial es upload_max_filesize, que determina el tamaño máximo permitido para un archivo individual subido. Si deseas permitir archivos de hasta 10 MB, debes configurarla así:

upload_max_filesize = 10M

Además, post_max_size controla el tamaño máximo de datos permitidos en una petición POST, incluyendo todos los campos del formulario y archivos adjuntos. Para asegurar que los archivos grandes se puedan subir correctamente, configura esta directiva a un valor mayor o igual al de upload_max_filesize:

post_max_size = 12M

La directiva max_file_uploads establece el número máximo de archivos que se pueden subir en una sola petición. Si tu formulario permite múltiples archivos, ajusta este valor según tus necesidades:

max_file_uploads = 20

Es importante también considerar la memoria que PHP tiene disponible para procesar las peticiones. La directiva memory_limit define el límite de memoria que un script puede utilizar. Para evitar errores al procesar archivos grandes, asegúrate de que este límite sea adecuado:

memory_limit = 128M

Después de realizar cambios en php.ini, es necesario reiniciar el servidor web integrado para que las nuevas configuraciones surtan efecto:

php -S localhost:8000

Finalmente, para ubicar el archivo php.ini, puedes usar el siguiente comando en la terminal, el cual mostrará la ruta del archivo de configuración que está utilizando PHP:

php -i | grep "Loaded Configuration File"

Asegurarse de que estas directivas estén correctamente configuradas es esencial para el correcto funcionamiento de las subidas de archivos en tus formularios PHP.

Subir archivos al servidor: uso de $_FILES

Para subir archivos desde un formulario HTML y manejarlos en PHP, es esencial comprender el uso de la superglobal $_FILES. Esta variable almacena información sobre los archivos que el usuario ha seleccionado para subir al servidor.

En primer lugar, el formulario HTML debe incluir el atributo enctype="multipart/form-data" para permitir la transmisión de archivos binarios:

<form action="upload.php" method="post" enctype="multipart/form-data">
    <label for="archivo">Selecciona un archivo:</label>
    <input type="file" name="archivo" id="archivo">
    <input type="submit" value="Subir archivo">
</form>

Al enviar el formulario, el archivo seleccionado se envía al script PHP especificado en el atributo action. En el script upload.php, se accede al archivo subido a través de $_FILES['archivo'].

La estructura de $_FILES es un array asociativo que contiene varias claves importantes:

  • $_FILES['archivo']['name']: nombre original del archivo en el ordenador del usuario.
  • $_FILES['archivo']['type']: tipo MIME del archivo (por ejemplo, image/jpeg).
  • $_FILES['archivo']['tmp_name']: nombre temporal del archivo en el servidor.
  • $_FILES['archivo']['error']: código de error asociado a la subida.
  • $_FILES['archivo']['size']: tamaño del archivo en bytes.

Un ejemplo básico de cómo manejar la subida y mover el archivo a un directorio específico:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $directorioSubida = 'uploads/';
    $nombreArchivo = basename($_FILES['archivo']['name']);
    $rutaCompleta = "$directorioSubida$nombreArchivo";

    if (move_uploaded_file($_FILES['archivo']['tmp_name'], $rutaCompleta)) {
        echo "El archivo se ha subido correctamente.\n";
    } else {
        echo "Ha ocurrido un error al subir el archivo.\n";
    }
}

En este código, se utiliza la función move_uploaded_file() para mover el archivo desde el directorio temporal al directorio deseado en el servidor. Es fundamental utilizar esta función, ya que garantiza que el archivo provenga de una subida válida y evita posibles vulnerabilidades.

El manejo del código de error en $_FILES['archivo']['error'] permite detectar problemas durante la subida. Los códigos de error pueden ser:

  • UPLOAD_ERR_OK (valor 0): sin errores.
  • UPLOAD_ERR_INI_SIZE (valor 1): el archivo excede el tamaño máximo definido en upload_max_filesize.
  • UPLOAD_ERR_FORM_SIZE (valor 2): el archivo excede el tamaño máximo especificado en el formulario.
  • Otros códigos que indican diferentes tipos de errores.

Es buena práctica verificar este código antes de procesar el archivo:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($_FILES['archivo']['error'] === UPLOAD_ERR_OK) {
        // Procesar el archivo
    } else {
        echo "Error al subir el archivo: " . $_FILES['archivo']['error'] . "\n";
    }
}

Al manejar archivos subidos, es esencial tener en cuenta el nombre del archivo para evitar sobrescrituras o conflictos. Una estrategia común es generar un nombre único utilizando funciones como uniqid() o concatenando con un timestamp:

<?php
$nombreUnico = uniqid() . "_$nombreArchivo";
$rutaCompleta = "$directorioSubida$nombreUnico";

Asimismo, es recomendable crear el directorio de subida si no existe. Esto se puede hacer con la función mkdir(), comprobando previamente si el directorio ya está creado:

<?php
if (!is_dir($directorioSubida)) {
    mkdir($directorioSubida, 0755);
}

En resumen, el uso de $_FILES permite acceder y manejar los archivos subidos por los usuarios de manera eficiente. Es importante manipular estos archivos con cuidado, siguiendo prácticas seguras y asegurándose de que el servidor esté configurado adecuadamente para recibir y almacenar los datos proporcionados.

Validación de tipo y tamaño de archivos

Al permitir que los usuarios suban archivos a través de un formulario, es fundamental realizar una validación exhaustiva tanto del tipo como del tamaño de los archivos para garantizar la seguridad y el correcto funcionamiento de la aplicación.

Para validar el tamaño de los archivos, se utiliza $_FILES['nombre_del_campo']['size'], que proporciona el tamaño en bytes. Es esencial verificar que el archivo no exceda el tamaño máximo permitido por la aplicación. Por ejemplo, para limitar el tamaño a 2 MB:

<?php
$tamanoMaximo = 2 * 1024 * 1024; // 2 MB en bytes

if ($_FILES['archivo']['size'] > $tamanoMaximo) {
    echo "El archivo excede el tamaño máximo permitido de 2 MB.\n";
    exit;
}

Además de esta comprobación, es recomendable asegurarse de que el servidor esté configurado adecuadamente, verificando las directivas upload_max_filesize y post_max_size en php.ini, como se ha visto en secciones anteriores.

Para la validación del tipo de archivo, es crucial evitar que se suban archivos potencialmente peligrosos. Existen varias formas de verificar el tipo MIME y la extensión del archivo, pero es importante no confiar únicamente en la información proporcionada por el cliente.

Una primera medida es comprobar la extensión del archivo. Por ejemplo, para permitir solo imágenes con extensiones .jpg, .png y .gif:

<?php
$extensionesPermitidas = ['jpg', 'jpeg', 'png', 'gif'];
$nombreArchivo = $_FILES['archivo']['name'];
$extension = strtolower(pathinfo($nombreArchivo, PATHINFO_EXTENSION));

if (!in_array($extension, $extensionesPermitidas)) {
    echo "El tipo de archivo no está permitido.\n";
    exit;
}

Sin embargo, la extensión puede ser fácilmente manipulada por el usuario, por lo que se debe complementar con otras validaciones. Otra opción es verificar el tipo MIME declarado en $_FILES['archivo']['type']. Aunque también puede ser falsificado, se puede utilizar como referencia adicional:

<?php
$tiposPermitidos = ['image/jpeg', 'image/png', 'image/gif'];
$tipoMime = $_FILES['archivo']['type'];

if (!in_array($tipoMime, $tiposPermitidos)) {
    echo "El tipo MIME del archivo no es válido.\n";
    exit;
}

Para una validación más robusta, es recomendable inspeccionar el contenido real del archivo. Por ejemplo, para imágenes, se puede utilizar la función getimagesize(), que devuelve información si el archivo es una imagen válida:

<?php
$datosImagen = getimagesize($_FILES['archivo']['tmp_name']);
if ($datosImagen === false) {
    echo "El archivo no es una imagen válida.\n";
    exit;
}

Para otros tipos de archivos, se puede utilizar finfo_file() junto con finfo_open(), lo que permite determinar el tipo MIME real del archivo de manera más confiable:

<?php
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$tipoMimeReal = finfo_file($finfo, $_FILES['archivo']['tmp_name']);
finfo_close($finfo);

$tiposPermitidos = ['application/pdf', 'image/jpeg', 'image/png'];

if (!in_array($tipoMimeReal, $tiposPermitidos)) {
    echo "El tipo de archivo no está permitido.\n";
    exit;
}

Esta técnica consulta la información real del archivo, reduciendo el riesgo de aceptar archivos maliciosos con extensiones cambiadas.

Lo más efectivo es combinar varias validaciones para asegurar que el archivo es seguro y cumple con los criterios de la aplicación:

<?php
$tamanoMaximo = 2 * 1024 * 1024; // 2 MB
$extensionesPermitidas = ['jpg', 'jpeg', 'png', 'gif'];
$tiposMimePermitidos = ['image/jpeg', 'image/png', 'image/gif'];

$nombreArchivo = $_FILES['archivo']['name'];
$extension = strtolower(pathinfo($nombreArchivo, PATHINFO_EXTENSION));
$tipoMimeDeclarado = $_FILES['archivo']['type'];

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$tipoMimeReal = finfo_file($finfo, $_FILES['archivo']['tmp_name']);
finfo_close($finfo);

if ($_FILES['archivo']['size'] > $tamanoMaximo) {
    echo "El archivo excede el tamaño máximo permitido.\n";
    exit;
}

if (!in_array($extension, $extensionesPermitidas)) {
    echo "La extensión del archivo no está permitida.\n";
    exit;
}

if (!in_array($tipoMimeReal, $tiposMimePermitidos)) {
    echo "El tipo MIME del archivo no es válido.\n";
    exit;
}

// Opcional: Verificar que el tipo MIME declarado coincida con el real
if ($tipoMimeDeclarado !== $tipoMimeReal) {
    echo "La información del tipo MIME no coincide.\n";
    exit;
}

// El archivo ha pasado todas las validaciones
echo "El archivo es válido y se puede procesar.\n";

Es importante considerar también la evitación de doble extensión en los nombres de archivo, como imagen.php.jpg, que podrían ser utilizados para intentar introducir código malicioso. Para evitar problemas con nombres de archivo potencialmente peligrosos, es recomendable generar nombres de archivo seguros y únicos, como se mencionó en secciones previas.

Además, se debe asegurar que el directorio de almacenamiento de los archivos subidos no sea accesible directamente desde la web, o configurar adecuadamente las políticas de acceso para evitar la ejecución de archivos subidos.

Al proporcionar mensajes al usuario, es aconsejable ser genérico y no revelar detalles específicos que puedan ser aprovechados por un atacante. Mensajes como "El archivo no es válido" son preferibles a descripciones detalladas del error.

En resumen, la validación exhaustiva del tipo y tamaño de archivos es esencial para mantener la seguridad y estabilidad de la aplicación. Al combinar múltiples métodos de validación y seguir buenas prácticas, se puede garantizar que los archivos subidos cumplan con los criterios establecidos y minimizar el riesgo de vulnerabilidades.

Prácticas recomendadas de seguridad al subir archivos

Al permitir que usuarios suban archivos a tu aplicación, es crucial seguir prácticas de seguridad para proteger tanto al servidor como a los datos. A continuación, se presentan recomendaciones esenciales para asegurar el manejo de archivos subidos en PHP.

1. Almacenar archivos fuera del directorio público

Es recomendable guardar los archivos subidos en un directorio que no sea accesible directamente desde la web. De esta forma, incluso si alguien intenta acceder al archivo directamente, no podrá hacerlo. Por ejemplo:

<?php
$directorioSeguro = __DIR__ . '/almacenamiento/'; // Directorio fuera del alcance público

if (!is_dir($directorioSeguro)) {
    mkdir($directorioSeguro, 0755);
}

// Procesamiento para mover el archivo al directorio seguro

Esto reduce el riesgo de que se ejecuten archivos maliciosos subidos por un atacante.

2. Renombrar los archivos subidos

Evita usar el nombre original del archivo proporcionado por el usuario. En su lugar, genera un nombre único para cada archivo, lo que previene conflictos y dificulta la identificación del archivo por parte de terceros. Por ejemplo:

<?php
$nombreUnico = bin2hex(random_bytes(16)); // Genera un nombre aleatorio seguro
$extension = pathinfo($_FILES['archivo']['name'], PATHINFO_EXTENSION);
$nombreArchivo = "$nombreUnico.$extension";

Esto también ayuda a evitar problemas con nombres que puedan contener caracteres especiales o rutas.

3. Configurar correctamente los permisos del directorio

Establece permisos adecuados para el directorio donde se almacenan los archivos, limitando las acciones que pueden realizarse. Un permiso de 0755 para directorios y 0644 para archivos suele ser suficiente, evitando que los archivos sean ejecutables:

<?php
chmod($directorioSeguro, 0755);
chmod($rutaCompletaArchivo, 0644);

Esto evita que los archivos subidos puedan ejecutarse como scripts en el servidor.

4. Validar el contenido del archivo

Además de verificar la extensión y el tipo MIME, es recomendable analizar el contenido real del archivo para asegurarse de que no contiene código malicioso. Por ejemplo, para imágenes, se puede utilizar getimagesize() o librerías como Imagick o GD para validar que el archivo es una imagen legítima.

5. Limitar los tipos de archivo permitidos

Restringe los tipos de archivos que se pueden subir a aquellos estrictamente necesarios para la funcionalidad de tu aplicación. Por ejemplo, si solo se necesitan imágenes JPEG y PNG, limita las subidas a estos tipos:

<?php
$tiposPermitidos = ['image/jpeg', 'image/png'];

if (!in_array($tipoMimeReal, $tiposPermitidos)) {
    echo "Tipo de archivo no permitido.\n";
    exit;
}

Esto reduce la superficie de ataque al evitar que se suban archivos potencialmente peligrosos.

6. Deshabilitar la ejecución de scripts en el directorio de archivos

Configura el servidor web para que no ejecute scripts en el directorio de almacenamiento de los archivos subidos. Por ejemplo, si usas Apache, puedes agregar un archivo .htaccess en el directorio que contenga:

php_flag engine off
Options -ExecCGI

Esto impide que se ejecuten archivos PHP o CGI en ese directorio.

7. Proteger contra ataques de path traversal

Evita que los usuarios puedan manipular la ruta de almacenamiento del archivo para acceder a otros archivos del sistema. Nunca utilices datos proporcionados por el usuario para construir rutas de archivo sin una validación estricta. Utiliza funciones como basename() para sanitizar nombres:

<?php
$nombreSeguro = basename($_FILES['archivo']['name']);

8. Utilizar tokens CSRF en formularios

Implementa tokens CSRF para proteger los formularios de subida contra ataques de Cross-Site Request Forgery. Esto asegura que solo los usuarios legítimos puedan realizar subidas de archivos:

<?php
// Al generar el formulario
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['token'] = $token;
?>
<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
    <!-- Resto del formulario -->
</form>

Y al procesar el formulario:

<?php
session_start();
if (!hash_equals($_SESSION['token'], $_POST['token'])) {
    echo "Solicitud inválida.\n";
    exit;
}

9. Limitar la velocidad y cantidad de subidas

Implementa mecanismos para evitar abusos, como limitar el número de subidas por usuario o implementar ratelimits. Esto puede prevenir ataques de denegación de servicio o consumo excesivo de recursos.

10. Monitorizar y registrar actividades

Mantén registros detallados de las actividades relacionadas con la subida de archivos. Registra información como la dirección IP, timestamp, archivos subidos, etc. Esto es útil para detectar y analizar comportamientos maliciosos.

11. Mantener el software actualizado

Asegúrate de que tu versión de PHP y todas las extensiones estén actualizadas. Las actualizaciones suelen incluir parches de seguridad que corrigen vulnerabilidades conocidas.

12. Manejar errores de forma segura

No proporciones mensajes de error detallados al usuario. En su lugar, muestra mensajes generales y registra los detalles técnicos en un log seguro:

<?php
if ($error) {
    error_log("Error al subir archivo: " . $detalleError);
    echo "Ocurrió un error al procesar tu solicitud.\n";
    exit;
}

Esto evita revelar información sensible que pueda ser aprovechada por un atacante.

13. Utilizar HTTPS

Implementa HTTPS para cifrar las comunicaciones entre el cliente y el servidor, protegiendo los datos que se transmiten, incluyendo los archivos subidos y tokens de sesión.

14. Revisar contenido potencialmente peligroso

Para ciertos tipos de archivos, considera implementar análisis adicionales, como escaneo antivirus o análisis de contenido para detectar códigos maliciosos incrustados.

15. Establecer límites en los nombres de archivos

Define restricciones en la longitud y caracteres permitidos en los nombres de archivos. Esto evita problemas de almacenamiento y posibles inyecciones:

<?php
$nombreArchivo = substr($nombreArchivo, 0, 250); // Limita a 250 caracteres
$nombreArchivo = preg_replace("/[^a-zA-Z0-9_\.-]/", "_", $nombreArchivo);
// Reemplaza caracteres no permitidos

16. Considerar el uso de servicios externos

Para reducir la carga y riesgos asociados, evalúa la posibilidad de utilizar servicios de almacenamiento externos o CDN que manejen la subida y entrega de archivos de forma segura.

17. Documentar y educar al equipo

Mantén una documentación actualizada sobre las prácticas de seguridad y asegúrate de que todo el equipo de desarrollo esté al tanto de las mismas.

Implementar estas prácticas recomendadas ayuda a fortalecer la seguridad de tu aplicación y protege tanto la integridad de los datos como la confidencialidad de los usuarios. La prevención y el enfoque proactivo son clave para minimizar riesgos y mantener la confianza en tu sistema.

Para seguir leyendo hazte Plus

¿Ya eres Plus? Accede a la app

20 % DE DESCUENTO

Plan mensual

19.00 /mes

15.20 € /mes

Precio normal mensual: 19 €
58 % DE DESCUENTO

Plan anual

10.00 /mes

8.00 € /mes

Ahorras 132 € al año
Precio normal anual: 120 €
Aprende PHP GRATIS online

Todas las lecciones de PHP

Accede a todas las lecciones de PHP y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Php

PHP

Introducción Y Entorno

Instalación Y Primer Programa De Php

PHP

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

PHP

Sintaxis

Operadores Y Expresiones

PHP

Sintaxis

Estructuras De Control

PHP

Sintaxis

Funciones Y Llamada De Funciones

PHP

Sintaxis

Cadenas De Texto Y Manipulación

PHP

Sintaxis

Manejo De Números

PHP

Sintaxis

Manejo De Fechas Y Tiempo

PHP

Sintaxis

Manejo De Arrays

PHP

Sintaxis

Introducción A La Poo En Php

PHP

Programación Orientada A Objetos

Clases Y Objetos

PHP

Programación Orientada A Objetos

Constructores Y Destructores

PHP

Programación Orientada A Objetos

Herencia

PHP

Programación Orientada A Objetos

Encapsulación

PHP

Programación Orientada A Objetos

Polimorfismo

PHP

Programación Orientada A Objetos

Interfaces

PHP

Programación Orientada A Objetos

Traits

PHP

Programación Orientada A Objetos

Namespaces

PHP

Programación Orientada A Objetos

Autoloading De Clases

PHP

Programación Orientada A Objetos

Manejo De Errores Y Excepciones

PHP

Programación Orientada A Objetos

Manejo De Archivos

PHP

Programación Orientada A Objetos

Patrones De Diseño

PHP

Programación Orientada A Objetos

Introducción A Los Formularios En Php

PHP

Formularios

Procesamiento De Datos De Formularios

PHP

Formularios

Manejo De Archivos En Formularios

PHP

Formularios

Redirecciones Y Retroalimentación Al Usuario

PHP

Formularios

Formularios Dinámicos Y Separación De Lógica

PHP

Formularios

Introducción A La Persistencia En Php

PHP

Persistencia

Conexión A Bases De Datos

PHP

Persistencia

Consultas Y Operaciones Crud

PHP

Persistencia

Gestión De Transacciones

PHP

Persistencia

Manejo De Errores Y Excepciones En Base De Datos

PHP

Persistencia

Patrones De Acceso A Datos

PHP

Persistencia

Concepto De Sesiones En Php

PHP

Sesiones Y Cookies

Configuración De Sesiones

PHP

Sesiones Y Cookies

Cookies

PHP

Sesiones Y Cookies

Manejo Avanzado De Sesiones Y Cookies

PHP

Sesiones Y Cookies

Principales Vulnerabilidades En Php

PHP

Seguridad

Seguridad En Formularios Y Entrada De Datos

PHP

Seguridad

Protección Frente A Inyección Sql

PHP

Seguridad

Gestión De Contraseñas Y Cifrado

PHP

Seguridad

Seguridad En Sesiones Y Cookies

PHP

Seguridad

Configuraciones De Php Para Seguridad

PHP

Seguridad

Introducción Al Testing En Php

PHP

Testing

Phpunit

PHP

Testing

Cobertura De Código En Testing

PHP

Testing

Test Doubles (Mocks, Stubs, Fakes, Spies)

PHP

Testing

Pruebas De Integración Y Funcionales

PHP

Testing

Accede GRATIS a PHP y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Configurar correctamente el archivo php.ini para permitir subidas de archivos.
  • Utilizar y entender la superglobal $_FILES en PHP.
  • Realizar validaciones exhaustivas del tipo y tamaño de los archivos subidos.
  • Aplicar prácticas recomendadas de seguridad al subir archivos.
  • Controlar la subida y manejo de archivos de manera segura y eficiente.