Jellyfin Server 12.2 - Alta - Remota - IDOR - GET /Items/Suggestions omit-userId vuelca la ruta y los IDs de la biblioteca oculta
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-suggestions-idor
Jellyfin Server 12.2 - Jellyfin
Clase: IDOR. Alcance: Remoto (cualquier usuario autenticado no administrador).
GET /Items/Suggestions solo tiene [Authorize]. Cuando se omite userId, SuggestionsController.GetSuggestions establece user = null y nunca llama a RequestHelpers.GetUserId. InternalItemsQuery(null) no llama a SetUser. LibraryManager.GetItemsResult omite AddUserToQuery. new DtoOptions() incluye ItemFields.Path. GetBaseItemDtos(..., user: null) omite IsVisible.
GHSA-9x85 enseñó a GetUserId (omitir va a los claims; un GUID ajeno es 403). ItemsController.GetItems siempre lo usa. Suggestions no lo hizo. El endpoint heredado GET /Users/{userId}/Suggestions siempre tiene un GUID en la ruta, por lo que ese hermano sí pasa por GetUserId.
Una cuenta restringida vuelca títulos, IDs, descripciones, IDs de proveedor y la ruta del sistema de archivos de bibliotecas a las que no está asignada. Esos GUIDs funcionan en GET /Videos/{id}/stream sin autenticación.
| ID | aún sin CVE |
| 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 |
| Producto | Jellyfin Server |
| Afectado | v12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6), imagen jellyfin/jellyfin:12.2 |
| Autenticación | autenticado (cualquier no administrador, incluida una cuenta infantil / de biblioteca oculta) |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1:18790 |
Residuo n.º 1 de Jellyfin 12.2 leftovers. Misma versión fijada, otros paquetes: jellyfin-svg-gzip (residuo de GHSA-cf3c gzip data:image/png), jellyfin-playlist-itemids (residuo de GHSA-9x85 ItemIds + stream sin autenticación), jellyfin-ffmpeg-colonslash (residuo de GHSA-wwwm comillas de URL .strm). Este paquete es el que vuelca la biblioteca oculta.
Cualquier usuario autenticado no administrador, incluida una cuenta infantil / de biblioteca oculta / con MaxParentalRating. Necesita un token de usuario normal. Sin administrador, sin clave de API. No cambian contraseñas. No crean administradores. Inventarían bibliotecas a las que no fueron asignados, incluida la ruta del sistema de archivos, y luego reproducen esos GUIDs.
Tabla de privilegios (exhaustiva). Mismo criterio que Signal-Server Dropwizard admin: método + ruta, qué concede esa llamada, reconocimiento vs integridad vs disponibilidad, probado en laboratorio vs inferido, superficie adyacente que permanece cerrada.
| Eje | Método + ruta | Otorga | Procedencia |
|---|---|---|---|
| Recon | GET /Items/Suggestions sin userId (opcional limit / mediaType / type) | Lista recursiva de elementos sin ámbito: Name, Id, Overview, IDs de proveedor, ruta del sistema de archivos, metadatos de imagen de bibliotecas a las que el llamador no está asignado, incluidas carpetas asignadas solo a un usuario oculto. No se aplican ACL de carpeta, clasificación parental ni etiquetas permitidas. | Probado en laboratorio. labuser restringido (EnableAllFolders=false, solo EnabledFolders=PublicB): GET /Items testigo=0; Suggestions testigo=1 |
| Recon | Igual con limit omitido | InternalItemsQuery.Limit queda sin establecer; el repositorio devuelve el conjunto completo de coincidencias (toda la tabla de elementos, orden aleatorio) | Fuente (SuggestionsController.cs:70-95) |
| Recon | GET /Videos/{itemId}/stream sin autenticación con un GUID de esa lista | Bytes de ese archivo como el UID de jellyfin | Fuente (VideosController no tiene [Authorize] a nivel de clase; StreamingHelpers GetItemById sin un usuario). El stream se probó en laboratorio en el residuo hermano jellyfin-playlist-itemids, no en este paquete |
| Integridad | ninguno en Suggestions | Sin escritura de contraseña, política, favorito ni sesión | Cerrado |
| Disponibilidad | ninguno en Suggestions | Sin DoS más allá del coste ordinario de consulta | Cerrado |
Permanece cerrado en este pin
| Eje | Método + ruta | Por qué está cerrado |
|---|---|---|
| Recon | GET /Items?userId= y GET /Items?parentId=<hiddenFolder> | Siempre GetUserId + IsVisible (ItemsController.cs:265, 342-350) |
| Recon | GET /Playlists/{id}/Items?userId= | GetUserId (PlaylistsController.cs:524). Clase GHSA-9x85, parcheado |
| Recon | GET /Items/Suggestions?userId=<other> | Un GUID ajeno sigue pasando por GetUserId y devuelve 403 para un no administrador. Probado en laboratorio (impersonate-403=yes) |
| Recon | GET /Users/{userId}/Suggestions heredado | Se requiere el GUID de la ruta, por lo que sí llama a GetUserId |
| Integridad | POST /Users/Password?userId= | AssertCanUpdateUser |
| Integridad | POST /Users/{id}/Policy | RequiresElevation |
| Integridad | dispositivos, sesiones, AddAdditionalUser | solo elevación o sesión propia |
| Recon | GET /Users / GET /Users/{id} | UserDto + política (GUIDs de carpeta, bit de administrador), no contenidos de biblioteca ni hashes. Enumeración del hogar, no este residuo |
No estoy afirmando RCE mediante DLL de plugin, una reverse shell ni robo de contraseñas. El volcado es el catálogo (y la ruta) de bibliotecas a las que el llamador no estaba asignado. El seguimiento de stream por GUID es un comportamiento de larga data de Jellyfin HTML5/DLNA; este residuo es lo que enumera los IDs.
v12.2 cerró la oleada de GHSA de 12.0. Aun así me quedé en esa etiqueta y leí cada consulta userId junto a RequestHelpers.GetUserId. Items siempre lo usa. Suggestions se ramifica: userId vacío -> user = null, nunca GetUserId. Ese es el residuo.
run.sh siembra dos bibliotecas de películas (PrivateA nombrada con el testigo, PublicB), completa el asistente, crea labuser con EnableAllFolders=false y solo EnabledFolders=PublicB, luego compara GET /Items contra GET /Items/Suggestions.
Falsos caminos ya registrados: tratar GET /Items como la fuga (está con ámbito); tratar Suggestions?userId=<admin> como la fuga (403); tratar la ruta heredada /Users/{userId}/Suggestions como el agujero (tiene un GUID, así que llama a GetUserId); afirmar que el stream sin autenticación es el fallo de este paquete (residuo hermano); una reverse shell. Teatro.