Jellyfin Server 12.2 - Alta - Remota - IDOR - GET /Items/Suggestions omit-userId espone il percorso e gli ID della libreria nascosta
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-suggestions-idor
Jellyfin Server 12.2 - Jellyfin
Classe: IDOR. Portata: Remota (qualsiasi utente autenticato non amministratore).
GET /Items/Suggestions è protetto solo da [Authorize]. Quando userId viene omesso, SuggestionsController.GetSuggestions imposta user = null e non chiama mai RequestHelpers.GetUserId. InternalItemsQuery(null) non esegue SetUser. LibraryManager.GetItemsResult salta AddUserToQuery. new DtoOptions() include ItemFields.Path. GetBaseItemDtos(..., user: null) salta IsVisible.
GHSA-9x85 ha introdotto GetUserId (se omesso ricade sui claims; un GUID estraneo restituisce 403). ItemsController.GetItems lo usa sempre. Suggestions no. Il legacy GET /Users/{userId}/Suggestions ha sempre un GUID nella route, quindi quella variante passa effettivamente da GetUserId.
Un account con restrizioni estrae titoli, ID, panoramiche, ID dei provider e Path del filesystem per librerie a cui non è assegnato. Quei GUID funzionano su GET /Videos/{id}/stream non autenticato.
| ID | nessuna CVE al momento |
| CWE | CWE-639 / CWE-862 |
| CVSS | Alto: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Prodotto | Jellyfin Server |
| Affetto | v12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6), immagine jellyfin/jellyfin:12.2 |
| Auth | autenticato (qualsiasi non amministratore, incluso un account bambino / libreria nascosta) |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1:18790 |
Residuo #1 di Jellyfin 12.2 leftovers. Stessa versione, altri pacchetti: jellyfin-svg-gzip (residuo GHSA-cf3c gzip data:image/png), jellyfin-playlist-itemids (residuo GHSA-9x85 ItemIds + stream non autenticato), jellyfin-ffmpeg-colonslash (residuo GHSA-wwwm virgolette URL .strm). Questo pacchetto è quello che estrae la libreria nascosta.
Qualsiasi utente autenticato non amministratore, incluso un account bambino / libreria nascosta / MaxParentalRating. Serve un normale token utente. Nessun amministratore, nessuna API key. Non cambiano le password. Non creano amministratori. Inventariano librerie a cui non sono stati assegnati, incluso il Path del filesystem, poi riproducono quei GUID.
Tabella dei privilegi (esaustiva). Stesso criterio di Signal-Server Dropwizard admin: metodo + percorso, cosa concede quella chiamata, ricognizione vs integrità vs disponibilità, dimostrato in lab vs dedotto, superficie adiacente che resta chiusa.
| Asse | Metodo + percorso | Concede | Provenienza |
|---|---|---|---|
| Recon | GET /Items/Suggestions senza userId (opzionali limit / mediaType / type) | Elenco ricorsivo di item non limitato all'ambito: Name, Id, Overview, ID dei provider, percorso filesystem, metadati delle immagini di librerie non assegnate al chiamante, incluse cartelle assegnate solo a un utente nascosto. ACL delle cartelle, classificazione per età e tag consentiti non vengono applicati. | Dimostrato in laboratorio. labuser limitato (EnableAllFolders=false, solo EnabledFolders=PublicB): GET /Items witness=0; Suggestions witness=1 |
| Recon | Lo stesso con limit omesso | InternalItemsQuery.Limit resta non impostato; il repository restituisce l'intero insieme di corrispondenze (l'intera tabella degli item, in ordine casuale) | Sorgente (SuggestionsController.cs:70-95) |
| Recon | GET /Videos/{itemId}/stream non autenticato con un GUID da quell'elenco | Byte di quel file con l'UID jellyfin | Sorgente (VideosController non ha [Authorize] a livello di classe; StreamingHelpers GetItemById senza un utente). Lo streaming è stato dimostrato in laboratorio nel residuato affine jellyfin-playlist-itemids, non in questo pacchetto |
| Integrità | nessuna su Suggestions | Nessuna scrittura di password, policy, preferiti o sessione | Chiuso |
| Disponibilità | nessuna su Suggestions | Nessun DoS oltre al normale costo della query | Chiuso |
Resta chiuso su questo pin
| Asse | Metodo + percorso | Perché è chiuso |
|---|---|---|
| Recon | GET /Items?userId= e GET /Items?parentId=<hiddenFolder> | Sempre GetUserId + IsVisible (ItemsController.cs:265, 342-350) |
| Recon | GET /Playlists/{id}/Items?userId= | GetUserId (PlaylistsController.cs:524). Classe GHSA-9x85, corretta |
| Recon | GET /Items/Suggestions?userId=<other> | Un GUID estraneo passa comunque per GetUserId e restituisce 403 per un non-admin. Dimostrato in laboratorio (impersonate-403=yes) |
| Recon | GET /Users/{userId}/Suggestions legacy | Il GUID nella route è obbligatorio, quindi chiama GetUserId |
| Integrità | POST /Users/Password?userId= | AssertCanUpdateUser |
| Integrità | POST /Users/{id}/Policy | RequiresElevation |
| Integrità | dispositivi, sessioni, AddAdditionalUser | solo elevazione o sessione propria |
| Recon | GET /Users / GET /Users/{id} | UserDto + policy (GUID delle cartelle, bit admin), non contenuti delle librerie o hash. Enumerazione dell'household, non questo residuato |
Non sto rivendicando RCE tramite DLL di plugin, una reverse shell o il furto di password. Il dump è il catalogo (e il Path) delle librerie non assegnate al chiamante. Il seguito dello streaming tramite GUID è un comportamento di lunga data di Jellyfin HTML5/DLNA; questo residuato è ciò che enumera gli ID.
La v12.2 ha chiuso l'ondata di GHSA della 12.0. Mi sono comunque fermato su quel tag e ho letto ogni query userId accanto a RequestHelpers.GetUserId. Items la usa sempre. Suggestions si dirama: userId vuoto -> user = null, mai GetUserId. Questo è il residuato.
run.sh prepara due librerie di film (PrivateA con il witness nel nome, PublicB), completa la procedura guidata, crea labuser con EnableAllFolders=false e solo EnabledFolders=PublicB, poi confronta GET /Items con GET /Items/Suggestions.
Vicoli ciechi già registrati: considerare GET /Items come la fuga (è limitato all'ambito); considerare Suggestions?userId=<admin> come la fuga (403); considerare la route legacy /Users/{userId}/Suggestions come la falla (ha un GUID quindi chiama GetUserId); dichiarare lo streaming non autenticato come bug di questo pacchetto (residuato affine); una reverse shell. Teatro.