La subida segura de archivos en PHP no consiste en aceptar un formulario y mover un fichero al servidor. Un adjunto puede convertirse en un vector de ejecución, una fuga de información, una carga que agota recursos o un documento inaccesible cuando el proceso de negocio lo necesita. El diseño debe cubrir el ciclo completo: recepción, validación, almacenamiento, procesamiento, autorización de acceso, auditoría y eliminación.
Definir el contrato de negocio antes de aceptar archivos

El primer control no es técnico: es limitar qué necesidad resuelve cada adjunto. Un justificante de identidad, una factura y una imagen de perfil tienen formatos, propietarios, periodos de conservación y permisos de consulta distintos. Agruparlos bajo una opción genérica de «subir archivo» dificulta aplicar controles adecuados.
Para cada tipo documental, defina un contrato explícito:
- Qué formatos son necesarios y cuáles quedan excluidos.
- Tamaño máximo, número máximo de adjuntos por operación y cuota acumulada por cuenta o expediente.
- Quién puede cargarlo, en qué estado del proceso y si puede reemplazarlo o eliminarlo.
- Qué validación automática y qué revisión humana necesita.
- Quién puede verlo, descargarlo o solicitar una nueva versión.
- Cuánto tiempo se conserva y qué evento activa su eliminación.
Este contrato evita aceptar archivos «por si acaso». También permite distinguir un fallo de validación, que el usuario puede corregir, de una restricción de negocio, como intentar adjuntar un documento cuando el expediente ya está cerrado.
Por qué extensión y tipo declarado no bastan
La extensión del nombre original y el tipo enviado en $_FILES['type'] son datos aportados por el cliente. Sirven como información auxiliar de interfaz, pero no prueban el contenido. Renombrar un archivo es trivial y un cliente puede enviar cualquier cabecera HTTP.
La validación técnica debe usar una defensa por capas. En PHP, detecte el tipo a partir de los bytes recibidos con mecanismos como finfo; después, aplique validadores propios del formato cuando el riesgo o el uso lo exijan. Para una imagen que se va a mostrar, no basta con identificarla como imagen: conviene decodificarla mediante una biblioteca adecuada y generar una nueva representación. Para un PDF o un documento ofimático, valide la estructura esperada con herramientas especializadas en un entorno aislado.
Una lista de permitidos es más segura que una lista de bloqueados. Si el caso de uso admite JPEG y PNG, rechace el resto en lugar de intentar enumerar formatos peligrosos. Los archivos comprimidos requieren un control adicional: un límite de tamaño comprimido no limita la expansión al descomprimir. Establezca topes de tamaño expandido, número de entradas, profundidad de anidamiento y tiempo de análisis.
Los nombres también son no confiables. No los use como ruta, identificador ni nombre físico. Secuencias como ../, caracteres de control, dobles extensiones y colisiones de nombres deben perder relevancia porque el sistema genera su propio identificador opaco.
Recepción controlada y gestión de errores parciales
Antes de procesar el contenido, limite la superficie de entrada. Configure límites coherentes en PHP, el servidor web y el proxy inverso. Si el proxy acepta 100 MB pero PHP admite 10 MB, el comportamiento será confuso; si PHP permite más de lo previsto, un atacante puede consumir memoria, disco temporal o conexiones de trabajo.
Controle de forma explícita el tamaño individual, el total de la petición, el número de archivos y la duración de carga. Compruebe el código de error de cada entrada de $_FILES, verifique que procede de una carga HTTP y trate cada adjunto como una unidad independiente. En una operación múltiple, decida de antemano si el resultado es atómico o parcial. Si se aceptan resultados parciales, la respuesta debe indicar con precisión qué archivo se recibió, cuál se rechazó y por qué, sin revelar detalles internos del servidor.
No procese un archivo directamente desde una ruta controlada por la petición ni confíe en que una carga interrumpida sea inocua. Los temporales incompletos se deben limpiar y los reintentos deben ser idempotentes cuando el producto los soporte. Un token de operación o una clave de idempotencia evita crear varios adjuntos equivalentes tras reenvíos de red.
Almacenamiento privado, cuarentena y procesamiento
Los binarios no deben quedar bajo el directorio público de la aplicación. Guárdelos en almacenamiento privado, con credenciales de mínimo privilegio, y asocie cada objeto a un identificador interno generado por el sistema. La base de datos puede conservar metadatos como propietario, contexto de negocio, tipo detectado, tamaño, huella criptográfica, estado, fechas y política de retención. Evite guardar datos personales innecesarios en el nombre o en registros operativos.
Un flujo robusto separa recepción y disponibilidad:
- La aplicación recibe el archivo y crea un registro en estado
pendienteoen_cuarentena. - El binario se deposita en una ubicación no accesible para descargas.
- Un proceso asíncrono realiza análisis antimalware, validación profunda, transformación o extracción permitida.
- El resultado cambia a
disponible,rechazadoorequiere_revision. - La interfaz muestra el estado operativo sin fingir que una carga ya es utilizable.
Las colas reducen el tiempo de respuesta, pero introducen fallos propios: trabajos duplicados, mensajes retrasados y procesadores caídos. Diseñe tareas idempotentes, límites de reintento, alertas para elementos atascados y una vía de reproceso controlada. Si el análisis no está disponible, la alternativa segura suele ser mantener el adjunto en cuarentena, no publicarlo.
Autorizar cada descarga y entregar contenido sin ejecutarlo
Que alguien conozca un identificador no le concede acceso. Cada descarga debe comprobar la identidad autenticada, su relación actual con el recurso y el contexto de negocio: pertenencia a una organización, asignación al expediente, rol vigente, estado del documento y restricciones temporales. No reutilice la autorización que existía al subir el archivo; los permisos pueden haberse revocado después.
La descarga debe pasar por un controlador que aplica esta decisión antes de leer o delegar el objeto. Las URL firmadas pueden ser útiles para entregar desde almacenamiento externo, pero requieren alcance limitado, caducidad corta y controles que impidan emitirlas para recursos no autorizados.
Entregue tipos activos con cautela. Para documentos no destinados a renderizarse en el navegador, use Content-Disposition: attachment. Establezca el tipo de contenido a partir del tipo validado, no de la extensión, y añada X-Content-Type-Options: nosniff. Una previsualización no es una simple descarga embebida: debe usar representaciones transformadas, aislar contenido potencialmente activo y evitar revelar rutas internas.
Auditoría, recuperación y pruebas antes de producción

Registre eventos relevantes: creación, reemplazo, cambio de estado, descarga, eliminación, error de análisis y modificación de permisos. El registro debe contener actor, momento, recurso y resultado, pero no necesita almacenar el binario ni datos sensibles duplicados. Proteja estos eventos contra alteraciones y defina quién puede consultarlos.
La recuperación exige saber qué ocurre si falla el almacenamiento, la base de datos o el procesador. No marque un documento como disponible hasta que el binario y sus metadatos estén consistentes. Diseñe tareas de conciliación para detectar registros sin objeto, objetos huérfanos y elementos que permanecen demasiado tiempo en cuarentena.
Lista de comprobación de salida
- Los formatos permitidos responden a un caso de uso documentado y se validan por contenido.
- Existen límites de tamaño, cantidad, tiempo y expansión de archivos comprimidos.
- Los nombres originales no determinan rutas ni nombres físicos.
- Los binarios se almacenan fuera del directorio público y pasan por cuarentena cuando corresponde.
- La descarga reevalúa autorización y no expone rutas internas.
- Se prueban archivos malformados, duplicados, cargas interrumpidas, permisos revocados y fallos de almacenamiento.
- Hay monitorización de rechazos, trabajos atascados, errores de entrega y capacidad consumida.
- Las políticas de retención, borrado y auditoría tienen responsable operativo.
Convertir una carga en un flujo con estados y controles puede parecer más costoso que usar un directorio de subidas. Sin embargo, reduce decisiones improvisadas cuando aparecen un archivo sospechoso, una reclamación de acceso o una interrupción operativa. Esa trazabilidad es parte del producto, no un añadido posterior.



