
Jellyfin Server 12.2 - Alta - Remoto após plantio de .strm - Injeção de argumento no FFmpeg - EncodingUtils -i remanescente de GHSA-wwwm
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-ffmpeg-colonslash
Classe: Injeção de argumentos no FFmpeg
Alcance: Remoto após a inserção de um .strm na biblioteca (quem escreve em uma pasta de mídia escaneada, depois stream sem autenticação)
Jellyfin Server v12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6) - Jellyfin. Imagem jellyfin/jellyfin:12.2.
GHSA-wwwm-px48-fpvq / PR 17685 adicionou EscapeProcessArgument no argv de conversão e no ramo -i de file:. Protocolos que não são File ainda colocam o caminho bruto entre aspas sem esse escape (EncodingUtils.cs:14-71). ManagedFileSystem.IsPathFile é falso para :// que não seja file://. TranscodeManager ainda monta ProcessStartInfo.Arguments como uma única string.
Um .strm de biblioteca escaneada cuja primeira linha é uma URL http contendo " é classificado como Http. Uri.TryCreate aceita essa URL. Um GET /Videos/{id}/stream sem autenticação (sem static) inicia o ffmpeg. O log do laboratório é:
-i "http://127.0.0.1/a" JELLYFIN-FFMPEG-COLONSLASH-WITNESS "x"
O controle LabControl.mkv permanece como um único argumento: -i file:"/media/movies/LabControl.mkv".
Um nome de arquivo POSIX com :// não atinge este sink. Barra não pode ser um nome de arquivo. A planta de laboratório a://b"…"x.mkv é armazenada como /media/movies/a:/b"…"x.mkv e segue o ramo File escapado. Não registre uma variante de nome de arquivo com dois-pontos e barra. Não estou alegando RCE via DLL de plugin. O laboratório prova apenas a divisão do argv.
Sobra #4 da série Jellyfin 12.2 leftovers.
| ID | ainda sem CVE |
| Classe | Injeção de argumentos no FFmpeg (CWE-88) |
| Alcance | Remoto após uma planta .strm na biblioteca, depois stream sem autenticação |
| CWE | CWE-88 |
| CVSS | Alto: 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N |
| Produto | Jellyfin Server |
| Afetado | v12.2 (ca0f16eb) imagem jellyfin/jellyfin:12.2 |
| Autenticação | escritor da biblioteca para a planta; o stream não é autenticado |
| Licença | GNU Affero GPL v3.0 |
| Laboratório | apenas 127.0.0.1 |
Mesmo modelo de planta do GHSA-wwwm: escrever um .strm em uma pasta de mídia escaneada (NAS / Samba / compartilhamento guest). Depois, qualquer um que consiga abrir TCP para o servidor pode fazer GET /Videos/{id}/stream sem Authorization e sem static. Esse é o conjunto completo de privilégios. Nada nesta lista é um shell ou RCE via DLL de plugin.
| Eixo | Método + caminho | Concede | Proveniência |
|---|---|---|---|
| Integridade / argv | GET /Videos/{id}/stream sem autenticação após uma planta .strm na biblioteca | Argumentos extras do ffmpeg fora das aspas do -i | Provado em laboratório. Log do TranscodeManager |
| Reconhecimento | o mesmo mais -vf drawtext=textfile= | Bytes de arquivo do host no transcode | Gadget inferido; não executado |
| Integridade | -attach / -dump_attachment | escrita com UID do jellyfin | Inferido; carregamento de plugin não demonstrado |
| RCE | não alegado | - | - |
ProcessBuilder. Sem RCE via DLL de plugin. A antiga cadeia GHSA-866x é histórica e não é demonstrada aqui.:// torna-se um diretório (a:) mais um arquivo. O Path canônico não tem ://, então atinge o ramo -i file: escapado.level do GHSA-jh22 está fechado.file:// de .strm são descartados (ProbeProvider mantém apenas http / https / rtsp / rtp).EncoderAppPath não pode ser definido via API de configuração de codificação..strm de upload de legenda (GHSA-j2hf) permanece fechado.Fixe a versão jellyfin/jellyfin v12.2. Vinte buscas no código-fonte, depois quatro laboratórios independentes em loopback. O PR 17685 escapou o argv de conversão e o -i file:. Os wrappers Http / rtsp foram deixados como "%s". A primeira planta que tentei foi um nome de arquivo POSIX contendo ://. Essa planta é um diretório. A sobra que dispara é um .strm cuja URL já contém :// e ".
Caminho errado, nessa ordem: tratar o atalho path.Contains("://") de GetFileInputArgument como um bug de nome de arquivo. Barra é um separador. O ramo File já escapa. O ramo não-File não escapa.
Irmãos nesta versão: jellyfin-suggestions-idor, jellyfin-svg-gzip, jellyfin-playlist-itemids.
Cliente loopback: jellyfin-ffmpeg-colonslash-Abraxas-Labs.py. Stack: lab/ (docker-compose.yml, poc.py, run.sh). Apenas laboratório autorizado. Ele se comunica com o loopback.
python3 jellyfin-ffmpeg-colonslash-Abraxas-Labs.py
Ou:
cd lab
./run.sh
Destino apenas http://127.0.0.1:18810. Imagem jellyfin/jellyfin:12.2. Projeto Compose jellyfin-ffmpeg-colonslash. Testemunha JELLYFIN-FFMPEG-COLONSLASH-WITNESS.
poc.py grava LabControl.mkv (controle, um -i file:"...") e LabPlant.strm cuja URL é http://127.0.0.1/a" JELLYFIN-FFMPEG-COLONSLASH-WITNESS "x. GET /Videos/{id}/stream sem autenticação (sem static) registra a quebra de aspas. Última linha:
SUCCESS jellyfin-ffmpeg-colonslash argv-split=yes plant-i=quote-break control-id=f603a52f649bcf822b333c4d5f21cc86 plant-id=d7aabf0935bb524e1667b1ff9bb945d9 JELLYFIN-FFMPEG-COLONSLASH-WITNESS
Chame EscapeProcessArgument em cada ramo de GetInputArgument, incluindo Http e rtsp. Mantenha o encapsulamento, mas faça o escape de aspas na string interna da mesma forma que o ramo file: já faz.