Gestión de archivos (File Management)

Por: Artiko
owaspseguridadhardeningfile-uploadpermisos

Gestión de archivos (FM)

La sección File Management del checklist cubre 5 puntos centrados en un vector de ataque clásico: los archivos que la aplicación sirve, recibe o almacena.

1. Desactivar el listado de directorios

Si un directorio no tiene un index.html (o equivalente) y el listado de directorios está activo, cualquiera puede ver —y potencialmente descargar— todos los archivos de esa carpeta.

# nginx
autoindex off;
# Apache
Options -Indexes

2. No guardar archivos subidos en el mismo contexto web que la aplicación

Los archivos que suben los usuarios (imágenes de perfil, adjuntos, documentos) no deberían vivir bajo la misma raíz web que sirve el código de la aplicación. Si conviven, un archivo malicioso subido por un usuario podría terminar ejecutándose como si fuera parte de la app.

La práctica recomendada es servir los uploads desde:

flowchart LR
    U[Usuario] -->|sube archivo| API[API de la aplicación]
    API -->|almacena| S3[(Bucket de uploads<br/>aislado, sin ejecución)]
    Nav[Navegador] -->|descarga directo| CDN[uploads.midominio.com]
    S3 --- CDN

3. Desactivar privilegios de ejecución en directorios de carga

Aunque los uploads compartan servidor, el directorio donde se guardan debe impedir que cualquier archivo subido se ejecute como script.

location /uploads/ {
    location ~ \.php$ { deny all; }
    location ~ \.(sh|py|rb|pl)$ { deny all; }
}

En el sistema de archivos, montar el directorio de uploads con la opción noexec:

mount -o noexec,nosuid,nodev /dev/sdb1 /var/www/uploads

4. Archivos y recursos de la aplicación en solo lectura

El código desplegado no debería ser modificable en runtime. Esto limita el impacto de una vulnerabilidad de escritura de archivos (por ejemplo, un path traversal o una deserialización insegura que intente sobreescribir un archivo .php o .js existente).

# Servidor tradicional
chown root:app -R /var/www/app
chmod -R 444 /var/www/app
# Docker Compose: montar el filesystem del contenedor como solo lectura
services:
  app:
    image: mi-app:latest
    read_only: true
    tmpfs:
      - /tmp

En Kubernetes, el equivalente es securityContext.readOnlyRootFilesystem: true en el manifiesto del Pod.

5. Restringir el acceso mediante listas de permitidos (allowlist)

El acceso a archivos y recursos —incluidos los que están fuera del control directo de la aplicación, como un proxy reverso o un CDN— debe basarse en una lista de lo permitido, no en una lista de lo prohibido. Es mucho más fácil olvidar bloquear un caso nuevo que olvidar permitir uno.

# Solo permite acceso desde la red interna a /internal/
location /internal/ {
    allow 10.0.0.0/8;
    deny all;
}
// Validar extensión de archivo contra una allowlist, no una blocklist
const EXTENSIONES_PERMITIDAS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.pdf']);

function esArchivoPermitido(nombre) {
  const ext = path.extname(nombre).toLowerCase();
  return EXTENSIONES_PERMITIDAS.has(ext);
}

Bloquear por extensión prohibida (.php, .exe, .sh, …) siempre deja hueco para lo que no se pensó bloquear; permitir solo lo conocido-bueno no.