Resumen
En dos sitios web de Essity, el endpoint POST /Umbraco/Api/ContactApi/SaveAttachment (parte del CMS Umbraco, pensado para adjuntar archivos a un formulario de contacto) aceptaba subidas de ficheros sin exigir autenticación, sin CAPTCHA y sin ningún límite de peticiones. Además respondía con la cabecera CORS access-control-allow-origin: *, de modo que cualquier página web del mundo podía lanzar la subida en segundo plano desde el navegador de una víctima.
La combinación de estos fallos convierte un formulario de contacto en un canal abierto para almacenar ficheros arbitrarios en el backend de Essity. La debilidad se clasificó como Missing Authentication for Critical Function (CWE-306), severidad alta, y el programa la marcó como Resolved.
Pasos de reproducción
1. Subida sin autenticar
Una simple petición POST con un fichero adjunto se aceptaba sin credenciales de ningún tipo. El servidor respondía con el nombre con el que había guardado el archivo:
curl -sk -X POST 'https://<sitio-essity>/Umbraco/Api/ContactApi/SaveAttachment' \
-H 'X-Requested-With: XMLHttpRequest' \
-F 'file=@/tmp/test.png;type=image/png;filename=test.png'
Respuesta:
["2e1ab0fe-db41-45ee-ad63-954edf814f77_test.png"]
El prefijo es un UUID que antepone el servidor; el fichero quedaba almacenado en el backend.
2. Aceptaba también PDF con JavaScript embebido
El endpoint no comprobaba el contenido real del fichero. Según el investigador, aceptaba igual de bien un PDF construido con una acción de apertura (/OpenAction) que ejecuta JavaScript al abrirlo en el lector, devolviendo de nuevo el nombre del fichero almacenado. No había ningún análisis del contenido que descartara este tipo de documento.
3. CORS abierto a cualquier origen
Una petición de preflight OPTIONS desde un origen arbitrario confirmaba que el servidor permitía peticiones cruzadas desde cualquier dominio:
curl -sk -X OPTIONS 'https://<sitio-essity>/Umbraco/Api/ContactApi/SaveAttachment' \
-H 'Origin: https://atacante.example' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: X-Requested-With'
Entre las cabeceras de respuesta:
access-control-allow-origin: *
Al no restringirse el origen, cualquier web podía realizar la subida vía fetch/XMLHttpRequest desde el navegador de quien la visitara, sin más interacción que entrar en la página del atacante.
4. Sin límite de peticiones
No existía ninguna limitación por IP ni por sesión: varias subidas concurrentes de ficheros grandes (hasta el máximo de 14 MB admitido) se aceptaban todas. El investigador comprobó que diez subidas simultáneas de 10 MB se completaban sin rechazo alguno.
5. Dos activos afectados
El mismo endpoint presentaba el fallo en dos sitios distintos de Essity, con resultados equivalentes en las pruebas de subida sin autenticar, PDF con JavaScript y CORS comodín.
Impacto
El investigador describe tres vías de abuso:
- Entrega de PDF maliciosos al personal. Cuando el equipo de backoffice revisa los envíos del formulario de contacto y abre un PDF adjunto, el JavaScript embebido se ejecuta en el contexto del lector de PDF. Según el reporte, esto podía escalarse a acciones como
app.launchURL()para phishing, o a intentos de exfiltración de ficheros locales desde la estación de trabajo del empleado; en visores de PDF dentro del navegador, a posible XSS en el origen del backoffice.
- Denegación de servicio por almacenamiento. Sin autenticación ni límite de peticiones, y con un tamaño máximo de 14 MB por fichero, un atacante podía escribir grandes volúmenes de datos en el almacenamiento de Essity desde una sola IP y a coste cero, con el único techo del ancho de banda.
- Explotación cruzada. Como CORS estaba abierto a cualquier origen, bastaba con que cualquier persona (empleado o no) visitara una página controlada por el atacante para que la subida ocurriera en segundo plano, de forma silenciosa.
Remediación
El investigador recomienda:
- Exigir autenticación (cookie de sesión) para subir ficheros.
- Validar un CAPTCHA/reCAPTCHA en el servidor.
- Aplicar límite de peticiones (por ejemplo, un máximo de subidas por IP y minuto).
- Analizar los ficheros subidos en busca de contenido malicioso (JavaScript en PDF, polyglots).
- Sustituir
access-control-allow-origin: *por una lista concreta de orígenes permitidos. - Imponer una cuota de tamaño por sesión o por IP.
Qué aprender de este caso
- Al auditar un CMS, los endpoints de API de adjuntos (en Umbraco, rutas bajo
/Umbraco/Api/...comoContactApi/SaveAttachment) son candidatos claros: comprueba si aceptanPOSTsin cookie de sesión y si devuelven el nombre del fichero guardado, señal de que la subida se ha completado. - Un formulario de contacto público no implica que su endpoint de subida deba ser anónimo: combina siempre autenticación o CAPTCHA con un límite de peticiones por IP, porque sin él la subida libre se convierte en una denegación de servicio por almacenamiento.
- Revisa la cabecera
access-control-allow-originen respuestas a un preflightOPTIONS: un*sobre un endpoint que escribe datos permite que cualquier web dispare la acción desde el navegador de la víctima. Devuelve solo orígenes de confianza explícitos. - No confíes en la extensión ni en el
Content-Typedeclarado: valida el contenido real del fichero y trata los PDF como ejecutables potenciales (pueden llevar/OpenActioncon JavaScript), avisando a quien los abra.