
Jellyfin Server 12.2 - Alta - Remota - SSRF - datos gzip:image/png restos de GHSA-cf3c aún obtiene http y file
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-svg-gzip
Clase: SSRF + lectura de archivos + memex
Alcance: Remoto (cualquier usuario autenticado POST /UserImage; la rasterización es no autenticada GET /UserImage)
Jellyfin Server 12.2 (ca0f16eb) - Jellyfin. Svg.Skia 3.7.0.
GHSA-cf3c-8m59-2vhx cerró los hrefs externos http y file en los SVG de perfil. 12.2 sigue fijando Svg.Skia 3.7.0. SvgDocument.ResolveExternalImages = ExternalType.None es ignorado por ImageDrawable de 3.7.0. SvgSecurityValidator.ValidateDataUri devuelve permitido para cualquier data URI cuyo tipo de medio no sea exactamente image/svg+xml, antes del recorrido de gzip. GetImageFromDataUri de Svg.Skia: mimeType.StartsWith("image/") más la firma mágica de gzip invoca LoadSvgz sin un re-escaneo de Jellyfin. Los hrefs http(s) / file del SVG interno llegan a WebRequest.Create. Ese es el sink original de GHSA-cf3c.
Cualquier usuario autenticado sube un SVG envuelto en gzip-png. Un GET no autenticado de esa imagen de perfil hace que el servidor obtenga http / https / file:// del atacante mientras rasteriza.
| ID | aún sin CVE |
| Clase | SSRF + lectura de archivos + memex |
| Alcance | Remoto. Cualquier usuario POST /UserImage. La rasterización es no autenticada GET /UserImage?userId= |
| CWE | CWE-918 / CWE-611 / CWE-400 |
| CVSS | Alto: 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:L |
| Producto | Jellyfin Server |
| Afectado | 12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6), imagen jellyfin/jellyfin:12.2, Svg.Skia 3.7.0 |
| Autenticación | Subida autenticada. Rasterización no autenticada. |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Resto #2 de la serie Jellyfin 12.2 leftovers. Hermano #1 Suggestions IDOR. Mismo pin: playlist ItemIds, FFmpeg colon-slash.
El actor es cualquier usuario de Jellyfin con sesión iniciada. La imagen de perfil no es exclusiva de administradores. La rasterización no necesita sesión.
| Eje | Método + ruta | Otorga | Procedencia |
|---|---|---|---|
| Reconocimiento | POST /UserImage (Content-Type: image/svg+xml, cuerpo BASE64) y luego GET /UserImage?userId= no autenticado | GET del servidor a http/https del atacante (loopback, RFC1918, link-local/IMDS) mientras Skia rasteriza | Probado en laboratorio. Nombre de host del oráculo oracle:8080, hits=1, testigo en la ruta |
| Reconocimiento | igual | Bytes de file:// pintados en el PNG/WebP/JPEG que descarga el solicitante, si se decodifican como imagen | Inferido. Mismo sink WebRequest.Create que GHSA-cf3c |
| Disponibilidad | igual, file:///dev/urandom interno | Lectura ilimitada retenida en el heap administrado. Se repite en cada GET posterior de la imagen sin volver a subir | Inferido. Medido en GHSA-cf3c |
| Integridad | ninguna | Sin escritura de políticas de usuario, sin instalación de plugins, sin robo de sesión | Cerrado |
Permanece cerrado: hrefs directos http/file en el SVG externo (lab: GET permaneció image/svg+xml, oracle 0); data:image/svg+xml anidado (reescaneado, incluido gzip); DTD SYSTEM; XSS inline de GHSA-vcmh (Content-Disposition: attachment más rasterización); traversal de imagen de plugin; Magick/PDF. No es RCE. No es XSS de sesión. El cuerpo de SSRF es lo que Skia pinta, no la respuesta HTTP en bruto.
12.2 es la etiqueta que ya incluía el pre-escaneo de GHSA-cf3c. Aun así lo dejé pasar. Los parches que añaden un validador delante de una biblioteca que sigue obteniendo recursos son un regalo: lees lo que recorre el validador y luego preguntas qué sigue analizando la biblioteca.
Directory.Packages.props sigue fijando Svg.Skia 3.7.0. Upstream wieslawsoltes/Svg.Skia#448 (corregido en #501): ImageDrawable nunca consulta ResolveExternalImages. Las tres asignaciones ExternalType.None de Jellyfin son un no-op en esta versión fijada. Renovate #17076 no está en la etiqueta.
El pre-escaneo es entonces la única barrera. ValidateReference rechaza todo href que no sea fragmento ni data:. El image/svg+xml anidado se vuelve a analizar, incluido gzip. El comentario en SvgSecurityValidator.cs:169-173 dice que cualquier otro tipo de medio es raster y devuelve permitir antes del recorrido de 0x1F 0x8B. Svg.Skia 3.7.0 GetImageFromDataUri no está de acuerdo: cualquier image/* con magia gzip es LoadSvgz.
Caminos equivocados ya registrados: tratar GHSA-cf3c como cerrado porque el control negativo permanece image/svg+xml; gzip anidado en data:image/svg+xml (reescaneado); DTD SYSTEM; XSS inline de GHSA-vcmh; traversal de imagen de plugin; Magick/PDF; RemoteImages/Download de admin (obtención de artwork prevista); un DoS por bomba de píxeles que no es este residuo; una reverse shell. Teatro.
python3 jellyfin-svg-gzip-Abraxas-Labs.py
o:
cd lab
./run.sh
Solo loopback: Jellyfin http://127.0.0.1:18780, oracle http://127.0.0.1:18781. Imagen jellyfin/jellyfin:12.2. El oracle devuelve un PNG de 1x1 y registra la ruta de la solicitud. El hostname de Compose oracle:8080 es lo que Skia obtiene.
Negativo: POST de un SVG de perfil cuyo href de imagen es http://oracle:8080/negative.png. GET permanece como image/svg+xml, oracle 0. Positivo: gzip de un SVG interno cuyo href es http://oracle:8080/JELLYFIN-SVG-SVGSKIA-WITNESS.png, envuelto como data:image/png;base64 en el SVG externo. GET es image/png, oracle 1.
SUCCESS jellyfin-svg-gzip gzip-png-ssrf hits=1 negative=0 JELLYFIN-SVG-SVGSKIA-WITNESS
Solo testigo. Sin payload de exploit. Sin reverse shell. Laboratorio autorizado. Loopback.
Recorrer gzip y data URIs anidados en SvgSecurityValidator incluso cuando el tipo de medio declarado no es image/svg+xml. O actualizar Svg.Skia más allá de la omisión de ExternalType.None en ImageDrawable (upstream #448 / #501; renovate #17076 no está en esta etiqueta). Ambas cosas. El flag de la biblioteca es el interruptor de seguridad previsto. El validador no debería mentir sobre lo que la biblioteca va a parsear.