
Una documentación de CVE-2025-68116
Autor: @x0root
Vulnerabilidad: Cross-Site Scripting (XSS) Almacenado mediante subidas renderizables por el navegador (SVG / HTML)
Software afectado: FileRise (< 2.7.1)
Versión parcheada: 2.7.1
CVE oficial (solicitado vía GHSA): CVE-2025-68116 (seguimiento/aviso: GHSA-35pp-ggh6-c59c)
Aviso relacionado previo (mitigación original que fue eludida): GHSA-qrcv-vjvf-fr29
Evaluación CVSS:
La evaluación del informante valora los Privilegios Requeridos (PR) en el punto de explotación, no en el punto de introducción de la vulnerabilidad.
La explotación ocurre cuando una víctima accede a un enlace de compartición pública generado, lo que no requiere autenticación ni privilegios (PR:N).
La evaluación del CNA valora PR basándose en la capacidad de subir un archivo malicioso. Sin embargo, CVSS v3.1 define los Privilegios Requeridos (PR) como los privilegios que un atacante debe poseer en el momento en que la vulnerabilidad es explotada, no los privilegios necesarios para colocar o preparar la condición vulnerable.
Como tal, PR:N refleja con mayor precisión las condiciones reales de explotación, lo que resulta en una clasificación de severidad Crítica (9.6).
Nota: GHSA-qrcv-vjvf-fr29 introdujo una mitigación que impedía que los SVG se renderizaran dentro de la interfaz web de FileRise (panel de vista previa). Este informe documenta una elusión de esa mitigación —específicamente los endpoints backend de compartición/descarga—, que se rastrea como GHSA-35pp-ggh6-c59c / CVE-2025-68116.
Este documento es un registro técnico completo de CVE-2025-68116: un XSS almacenado en FileRise que persistió después de una mitigación anterior y finalmente se corrigió en v2.7.1. Incluye el descubrimiento, pruebas de concepto de explotación, múltiples correcciones fallidas, un análisis preciso del flujo de control de causa raíz (con evidencia), la verificación final del parche y un análisis de las características de explotabilidad relevantes para la evaluación CVSS. Todo el contenido a continuación se basa en pruebas reproducidas, inspección del controlador y el hilo del aviso público.
Un aviso previo, GHSA-qrcv-vjvf-fr29, abordó el XSS almacenado mediante subidas de SVG bloqueando la renderización en línea en la interfaz web de FileRise. Esa mitigación no abordó cómo los archivos SVG eran servidos por endpoints backend como:
/api/file/download.php/api/file/share.phpCVE-2025-68116 (rastreado como GHSA-35pp-ggh6-c59c) documenta una elusión de la mitigación de GHSA-qrcv-vjvf-fr29: un atacante puede almacenar un SVG manipulado y entregarlo a las víctimas mediante enlaces de compartición pública o ciertos comportamientos de descarga, lo que lleva a la ejecución de scripts en el origen de FileRise.
Para validar si el backend todavía exponía los SVG de forma que pudieran renderizarse, subí un SVG PoC simple:
El acceso al archivo mediante:
/api/file/download.php?…/api/file/share.php?token=…resultó en la ejecución de alert(). La mitigación original de GHSA-qrcv-vjvf-fr29 (bloqueo de vista previa en la interfaz) fue eludida mediante acceso directo a estos endpoints.
Un alert() es una prueba de concepto; probé el impacto real haciendo que el payload interactuara con las APIs internas.
Payload de prueba utilizado:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
Cuando un administrador con sesión iniciada abría un enlace de compartición que contenía este SVG, el script se ejecutaba y realizaba solicitudes API autenticadas. Los efectos observados incluyeron:
{"csrf_expired":true,"csrf_token":"..."})Clasificación de impacto demostrada durante las pruebas:
Informé del problema de forma privada. El mantenedor publicó varias correcciones incrementales:
Desde v2.6.0 → v2.7.0, el endpoint de enlaces de compartición continuó sirviendo el SVG de una manera que permitía la renderización en línea y la ejecución de scripts. El análisis de causa raíz a continuación explica por qué las correcciones anteriores no lograron cerrar por completo el vector.
La causa subyacente no era una única cabecera faltante, sino el flujo de control y el orden de la salida dentro de shareFile() (controlador), que impedía que las cabeceras de seguridad se aplicaran en muchas rutas de ejecución. Había dos clases de problemas:
exit; tempranos que cortocircuitaban la función antes de que se establecieran las cabeceras de seguridad.Utilicé un escaneo con awk para listar las apariciones de header() y exit; dentro de shareFile() hasta la llamada a readfile():
Comando: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
Salida observada (abreviada de mi ejecución):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Las cabeceras de seguridad (la lógica de endurecimiento) comienzan en la línea ~1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
Debido a que la función emite cabeceras + exit; antes en muchas rutas, esas solicitudes nunca llegaban al código de endurecimiento que establece Content-Disposition, nosniff o el tipo restrictivo.
En el flujo de compartición protegida por contraseña, la función emitía temprano el HTML de solicitud de contraseña:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
Esta ruta envía Content-Type: text/html y sale antes de la lógica de endurecimiento SVG, causando la renderización en línea en los navegadores para comparticiones protegidas por contraseña cuando no se proporcionaba la contraseña.
La vulnerabilidad no se limitaba a los flujos protegidos por contraseña. Una solicitud de compartición sin contraseña también devolvía text/html en mis pruebas:
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
Observado: < Content-Type: text/html; charset=UTF-8
Esto confirma que incluso en el caso general (sin contraseña) la respuesta era text/html, y el SVG se renderizaba en línea.
Capturé una solicitud cruda al endpoint de compartición que incluía advertencias de PHP emitidas antes del endurecimiento de cabeceras. Instantánea (abreviada):
Comando: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
Salida cruda observada (abreviada):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
Estos avisos muestran que se producía salida (notificaciones de deprecación) antes del endurecimiento de cabeceras, haciendo imposible que las llamadas posteriores a header() surtieran efecto en esas ejecuciones.
nosniff existía, pero no se alcanzaba en muchas rutas de código debido a salidas tempranas y salida.Tras los informes de causa raíz, el mantenedor aplicó cambios que abordaron el flujo de control y el orden de la salida. En v2.7.1:
exit; tempranos que eludían el endurecimiento se corrigieron/manejaron.Verificación final (mi prueba en v2.7.1):
Comando: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
Observado: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
Resultado: el navegador forzó una descarga; el SVG no se renderizó en línea y el payload XSS no se ejecutó. Considero que v2.7.1 resolvió el problema en mi entorno.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — Puntuación: 9.6 (Crítica)
Documenté esta justificación en el hilo del aviso y solicité que se usara PR:N.
El vector CVSS del CNA refleja un modelo de amenaza restringido.
Las pruebas empíricas demuestran una ruta de explotación más severa y reproducible, que se alinea con una puntuación base CVSS 3.1 más alta según las reglas de puntuación estándar.
Se anima a los evaluadores independientes a evaluar la severidad utilizando las condiciones de explotación observadas descritas en este documento, asegurando que la severidad pública refleje el impacto real en lugar de una línea base de alcance estrecho.
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
Las cabeceras de seguridad comienzan en ~1743: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...seguido del payload SVG que se imprimía y se renderizaba en línea.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg