php-como-subir-archivos-de-forma-segura-con-php

Cómo subir archivos de forma segura con PHP

  • 3 min

La subida de archivos permite recibir en el servidor un fichero enviado mediante un formulario HTTP.

Subir archivos implica dos partes: el formulario HTML que envía el archivo y el script PHP que lo recibe, lo valida y lo mueve a su lugar definitivo.

El formulario HTML (el requisito olvidado)

Para subir archivos, la etiqueta <form> debe tener dos atributos obligatorios. Si olvidas uno, el archivo nunca llegará a PHP.

  1. method="POST": Los archivos no viajan por GET.
  2. enctype="multipart/form-data": Indica al navegador que construya una petición capaz de incluir archivos.
<form action="upload.php" method="POST" enctype="multipart/form-data">
    <label>Selecciona una imagen:</label>
    <input type="file" name="fichero_usuario">
    <button type="submit">Subir</button>
</form>
Copied!

La superglobal $_FILES

Cuando PHP recibe el archivo, no lo pone en $_POST. Lo pone en $_FILES. Es un array bidimensional con 5 datos clave por cada archivo subido:

Si tu input se llama name="fichero_usuario", tendrás:

ÍndiceDescripciónImportancia
$_FILES['fichero_usuario']['name']El nombre original (ej: foto.jpg).Peligroso: El usuario puede manipularlo.
$_FILES['fichero_usuario']['type']El tipo de archivo (ej: image/jpeg).Peligroso: El navegador puede mentir.
$_FILES['fichero_usuario']['tmp_name']Dónde lo guardó PHP temporalmente.Es la ruta que usaremos para validarlo y moverlo.
$_FILES['fichero_usuario']['error']Código de error (0 = Éxito).Debes verificar que sea 0.
$_FILES['fichero_usuario']['size']Tamaño en bytes.Úsalo para validar límites.

El proceso de seguridad (paso a paso)

Nunca muevas el archivo directamente. Sigue este checklist de seguridad:

Verificar Errores: Comprueba si ['error'] === UPLOAD_ERR_OK.

Validar Tamaño: No permitas que te llenen el disco duro.

Validar Tipo (MIME): Asegúrate de que es una imagen real y no un script .php disfrazado.

Renombrar el archivo: nunca guardes el archivo con el nombre original.

  • Riesgo 1: Sobreescritura (dos usuarios suben foto.jpg).
  • Riesgo 2: Hackeo (un usuario sube hack.php y luego intenta ejecutarlo).
  • Solución: Genera un nombre aleatorio con random_bytes() y asigna la extensión a partir del MIME verificado.

Ejemplo completo y seguro

Este script implementa todas las barreras de seguridad mencionadas.

// Configuración
$directorioSubida = 'uploads/';
$maxSize = 2 * 1024 * 1024; // 2 MB

// Asegurarse de que se envió el archivo
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['fichero_usuario'])) {
    
    $archivo = $_FILES['fichero_usuario'];

    // 1. Verificar errores de subida
    if ($archivo['error'] !== UPLOAD_ERR_OK) {
        die("Error al subir el archivo.");
    }

    // 2. Validar tamaño
    if ($archivo['size'] > $maxSize) {
        die("Error: El archivo es demasiado grande (Máx 2MB).");
    }

    // 3. Validar tipo de archivo (Seguridad Real)
    // No confíes en $archivo['type'], usa finfo para ver el contenido real
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $tipoMime = $finfo->file($archivo['tmp_name']);

    $formatosPermitidos = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'application/pdf' => 'pdf',
    ];

    if (!isset($formatosPermitidos[$tipoMime])) {
        die("Error: Formato de archivo no permitido.");
    }

    // 4. Generar nombre y extensión desde datos de confianza
    $extension = $formatosPermitidos[$tipoMime];
    $nombreNuevo = bin2hex(random_bytes(16)) . '.' . $extension;
    $rutaDestino = $directorioSubida . $nombreNuevo;

    // 5. Mover del temporal al definitivo
    if (move_uploaded_file($archivo['tmp_name'], $rutaDestino)) {
        echo "¡Éxito! Archivo guardado como: " . $nombreNuevo;
    } else {
        echo "Error al mover el archivo.";
    }
}
Copied!

Guarda las subidas fuera del directorio público siempre que sea posible. Si tienen que ser públicas, configura el servidor para impedir la ejecución de scripts en esa carpeta y sirve los archivos con las cabeceras adecuadas.

Si intentas subir un archivo grande y falla sin error, revisa tu archivo php.ini. Las directivas upload_max_filesize y post_max_size suelen venir limitadas a 2MB por defecto.