
Jellyfin Server 12.2 - Élevée - Distante - SSRF - données gzip data:image/png résiduelles de GHSA-cf3c récupèrent encore http et file
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-svg-gzip
Classe : SSRF + lecture de fichier + memex
Portée : Distante (tout utilisateur authentifié POST /UserImage ; la rastérisation est non authentifiée GET /UserImage)
Jellyfin Server 12.2 (ca0f16eb) - Jellyfin. Svg.Skia 3.7.0.
GHSA-cf3c-8m59-2vhx a fermé les hrefs externes http et file sur les SVG de profil. La 12.2 épingle toujours Svg.Skia 3.7.0. SvgDocument.ResolveExternalImages = ExternalType.None est ignoré par ImageDrawable de la 3.7.0. SvgSecurityValidator.ValidateDataUri renvoie « autorisé » pour toute data URI dont le type MIME n'est pas exactement image/svg+xml, avant le parcours gzip. GetImageFromDataUri de Svg.Skia : mimeType.StartsWith("image/") plus la signature magique gzip appelle LoadSvgz sans nouvelle analyse par Jellyfin. Les hrefs http(s) / file du SVG interne aboutissent à WebRequest.Create. C'est le sink d'origine de GHSA-cf3c.
Tout utilisateur authentifié téléverse un SVG encapsulé dans un gzip-png. Un GET non authentifié de cette image de profil fait que le serveur récupère les http / https / file:// de l'attaquant pendant la rastérisation.
| ID | pas encore de CVE |
| Classe | SSRF + lecture de fichier + memex |
| Portée | Distante. Tout utilisateur POST /UserImage. La rastérisation est non authentifiée GET /UserImage?userId= |
| CWE | CWE-918 / CWE-611 / CWE-400 |
| CVSS | Élevé : 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:L |
| Produit | Jellyfin Server |
| Affecté | 12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6), image jellyfin/jellyfin:12.2, Svg.Skia 3.7.0 |
| Auth | Téléversement authentifié. Rastérisation non authentifiée. |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Reliquat #2 de la série Jellyfin 12.2 leftovers. Frère #1 Suggestions IDOR. Même épinglage : playlist ItemIds, FFmpeg colon-slash.
L'acteur est tout utilisateur Jellyfin connecté. L'image de profil n'est pas réservée aux administrateurs. La rastérisation ne nécessite pas de session.
| Axe | Méthode + chemin | Accorde | Provenance |
|---|---|---|---|
| Reconnaissance | POST /UserImage (Content-Type: image/svg+xml, corps BASE64) puis GET /UserImage?userId= non authentifié | GET serveur des http/https de l'attaquant (loopback, RFC1918, link-local/IMDS) pendant que Skia rastérise | Prouvé en lab. Nom d'hôte oracle oracle:8080, hits=1, témoin dans le chemin |
| Reconnaissance | idem | Octets file:// peints dans le PNG/WebP/JPEG que le demandeur télécharge, s'ils se décodent comme une image | Déduit. Même sink WebRequest.Create que GHSA-cf3c |
| Disponibilité | idem, file:///dev/urandom interne | Lecture non bornée retenue sur le tas managé. Se répète à chaque GET ultérieur de l'image sans nouveau téléversement | Déduit. Mesuré dans GHSA-cf3c |
| Intégrité | aucune | Aucune écriture de politique utilisateur, aucune installation de plugin, aucun vol de session | Fermé |
Reste fermé : les hrefs directs http/file sur le SVG externe (lab : GET est resté image/svg+xml, oracle 0) ; data:image/svg+xml imbriqué (re-scanné, y compris gzip) ; DTD SYSTEM ; XSS inline GHSA-vcmh (Content-Disposition: attachment plus rastérisation) ; traversée d'image de plugin ; Magick/PDF. Pas de RCE. Pas de XSS de session. Le corps SSRF est ce que Skia peint, pas la réponse HTTP brute.
12.2 est le tag qui a déjà livré le pré-scan GHSA-cf3c. Je l'ai quand même laissé passer. Les correctifs qui ajoutent un validateur devant une bibliothèque qui récupère encore sont un cadeau : on lit ce que le validateur parcourt, puis on demande ce que la bibliothèque parse encore.
Directory.Packages.props épingle toujours Svg.Skia 3.7.0. En amont wieslawsoltes/Svg.Skia#448 (corrigé dans #501) : ImageDrawable ne consulte jamais ResolveExternalImages. Les trois affectations ExternalType.None de Jellyfin sont sans effet sur cet épinglage. Renovate #17076 n'est pas dans le tag.
Le pré-scan est alors la seule barrière. ValidateReference rejette tout href non-fragment et non-data:. Le image/svg+xml imbriqué est re-parsé, y compris gzip. Le commentaire à SvgSecurityValidator.cs:169-173 indique que tout autre type de média est rastérisé et renvoie allow avant le parcours 0x1F 0x8B. Svg.Skia 3.7.0 GetImageFromDataUri n'est pas d'accord : tout image/* avec la magie gzip est LoadSvgz.
Fausses pistes déjà consignées : traiter GHSA-cf3c comme clos parce que le contrôle négatif reste image/svg+xml ; gzip data:image/svg+xml imbriqué (re-scanné) ; DTD SYSTEM ; XSS inline GHSA-vcmh ; traversée d'image de plugin ; Magick/PDF ; RemoteImages/Download admin (récupération d'illustration prévue) ; un DoS par pixel-bomb qui n'est pas ce reliquat ; un reverse shell. Du théâtre.
python3 jellyfin-svg-gzip-Abraxas-Labs.py
ou :
cd lab
./run.sh
Loopback uniquement : Jellyfin http://127.0.0.1:18780, oracle http://127.0.0.1:18781. Image jellyfin/jellyfin:12.2. L'oracle renvoie un PNG 1x1 et journalise le chemin de la requête. Le nom d'hôte Compose oracle:8080 est celui que Skia récupère.
Négatif : POST d'un SVG de profil dont le href d'image est http://oracle:8080/negative.png. GET reste image/svg+xml, oracle 0. Positif : gzip d'un SVG interne dont le href est http://oracle:8080/JELLYFIN-SVG-SVGSKIA-WITNESS.png, encapsulé en data:image/png;base64 sur le SVG externe. GET est image/png, oracle 1.
SUCCESS jellyfin-svg-gzip gzip-png-ssrf hits=1 negative=0 JELLYFIN-SVG-SVGSKIA-WITNESS
Témoin uniquement. Aucune charge utile d'exploit. Aucun reverse shell. Laboratoire autorisé. Loopback.
Parcourir gzip et les data URIs imbriqués dans SvgSecurityValidator même lorsque le type de média déclaré n'est pas image/svg+xml. Ou mettre à jour Svg.Skia au-delà de l'ignorance de ExternalType.None dans ImageDrawable (amont #448 / #501 ; renovate #17076 n'est pas dans ce tag). Les deux. Le drapeau de la bibliothèque est le kill switch prévu. Le validateur ne devrait pas mentir sur ce que la bibliothèque va analyser.