
Jellyfin Server 12.2 - Alta - Remota - SSRF - dados gzip:image/png remanescentes de GHSA-cf3c ainda buscam http e file
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-svg-gzip
Classe: SSRF + leitura de arquivo + memex
Alcance: Remoto (qualquer usuário autenticado POST /UserImage; rasterização é não autenticada GET /UserImage)
Jellyfin Server 12.2 (ca0f16eb) - Jellyfin. Svg.Skia 3.7.0.
GHSA-cf3c-8m59-2vhx fechou hrefs externos http e file em SVGs de perfil. A 12.2 ainda fixa Svg.Skia 3.7.0. SvgDocument.ResolveExternalImages = ExternalType.None é ignorado pelo ImageDrawable da 3.7.0. SvgSecurityValidator.ValidateDataUri retorna permitido para qualquer data URI cujo tipo de mídia não seja exatamente image/svg+xml, antes da verificação de gzip. O GetImageFromDataUri do Svg.Skia: mimeType.StartsWith("image/") mais magic de gzip chama LoadSvgz sem nova verificação do Jellyfin. Os hrefs http(s) / file do SVG interno vão para WebRequest.Create. Esse é o sink original do GHSA-cf3c.
Qualquer usuário autenticado envia um SVG empacotado em gzip-png. Um GET não autenticado dessa imagem de perfil faz o servidor buscar http / https / file:// do atacante durante a rasterização.
| ID | sem CVE ainda |
| Classe | SSRF + leitura de arquivo + memex |
| Alcance | Remoto. Qualquer usuário POST /UserImage. Rasterização é não 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 |
| Produto | Jellyfin Server |
| Afetado | 12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6), imagem jellyfin/jellyfin:12.2, Svg.Skia 3.7.0 |
| Autenticação | Upload autenticado. Rasterização não autenticada. |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1 |
Sobra #2 da série Jellyfin 12.2 leftovers. Irmã #1 Suggestions IDOR. Mesmo pin: playlist ItemIds, FFmpeg colon-slash.
O ator é qualquer usuário autenticado do Jellyfin. A imagem de perfil não é restrita a administradores. A rasterização não precisa de sessão.
| Eixo | Método + caminho | Concede | Procedência |
|---|---|---|---|
| Recon | POST /UserImage (Content-Type: image/svg+xml, corpo BASE64) e depois GET /UserImage?userId= não autenticado | GET do servidor para http/https do atacante (loopback, RFC1918, link-local/IMDS) durante a rasterização do Skia | Comprovado em lab. Hostname oracle oracle:8080, hits=1, testemunha no caminho |
| Recon | idem | bytes de file:// pintados no PNG/WebP/JPEG que o requisitante baixa, se decodificarem como imagem | Inferido. Mesmo sink WebRequest.Create do GHSA-cf3c |
| Disponibilidade | idem, file:///dev/urandom interno | Leitura ilimitada retida no heap gerenciado. Repete em cada GET posterior da imagem sem novo upload | Inferido. Medido no GHSA-cf3c |
| Integridade | nenhum | Nenhuma escrita de política de usuário, nenhuma instalação de plugin, nenhum roubo de sessão | Fechado |
Permanece fechado: hrefs diretos http/file no SVG externo (lab: GET permaneceu image/svg+xml, oracle 0); data:image/svg+xml aninhado (re-escaneado, incluindo gzip); DTD SYSTEM; XSS inline GHSA-vcmh (Content-Disposition: attachment mais rasterização); travessia de imagem de plugin; Magick/PDF. Não é RCE. Não é XSS de sessão. O corpo do SSRF é o que o Skia pinta, não a resposta HTTP bruta.
12.2 é a tag que já entregou o pré-scan do GHSA-cf3c. Mesmo assim, eu deixei passar. Patches que adicionam um validador na frente de uma biblioteca que ainda busca são um presente: você lê o que o validador percorre, depois pergunta o que a biblioteca ainda analisa.
Directory.Packages.props ainda fixa Svg.Skia 3.7.0. Upstream wieslawsoltes/Svg.Skia#448 (corrigido em #501): ImageDrawable nunca consulta ResolveExternalImages. As três atribuições ExternalType.None do Jellyfin são um no-op nesta fixação. O Renovate #17076 não está na tag.
O pré-scan é então o único portão. ValidateReference rejeita todo href que não seja fragmento nem data:. image/svg+xml aninhado é re-analisado, incluindo gzip. O comentário em SvgSecurityValidator.cs:169-173 diz que qualquer outro tipo de mídia é raster e retorna permitir antes da varredura 0x1F 0x8B. O GetImageFromDataUri do Svg.Skia 3.7.0 discorda: qualquer image/* com magic gzip é LoadSvgz.
Caminhos errados já registrados: tratar o GHSA-cf3c como fechado porque o controle negativo permanece image/svg+xml; gzip data:image/svg+xml aninhado (re-escaneado); DTD SYSTEM; XSS inline GHSA-vcmh; travessia de imagem de plugin; Magick/PDF; RemoteImages/Download do admin (busca de artwork pretendida); um DoS de pixel-bomb que não é este resíduo; um reverse shell. Teatro.
python3 jellyfin-svg-gzip-Abraxas-Labs.py
ou:
cd lab
./run.sh
Apenas loopback: Jellyfin http://127.0.0.1:18780, oracle http://127.0.0.1:18781. Imagem jellyfin/jellyfin:12.2. O oracle retorna um PNG 1x1 e registra o caminho da requisição. O hostname do Compose oracle:8080 é o que o Skia busca.
Negativo: POST de um SVG de perfil cujo href da imagem é http://oracle:8080/negative.png. O GET permanece image/svg+xml, oracle 0. Positivo: gzip de um SVG interno cujo href é http://oracle:8080/JELLYFIN-SVG-SVGSKIA-WITNESS.png, encapsulado como data:image/png;base64 no SVG externo. O GET é image/png, oracle 1.
SUCCESS jellyfin-svg-gzip gzip-png-ssrf hits=1 negative=0 JELLYFIN-SVG-SVGSKIA-WITNESS
Apenas testemunha. Sem payload de exploit. Sem reverse shell. Laboratório autorizado. Loopback.
Percorra gzip e data URIs aninhados em SvgSecurityValidator mesmo quando o tipo de mídia declarado não for image/svg+xml. Ou atualize o Svg.Skia para além da ignorância de ExternalType.None do ImageDrawable (upstream #448 / #501; o renovate #17076 não está nesta tag). Ambos. A flag da biblioteca é o kill switch pretendido. O validador não deve mentir sobre o que a biblioteca irá analisar.