abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-playlist-itemids
类别: IDOR 影响范围: 远程(任何能看到公开播放列表的用户)
Jellyfin Server v12.2(ca0f16eb7c195f720a1493ed469a27c4657db0c6)- Jellyfin
GHSA-9x85 为 GET /Playlists/{id}/Items 修补了 GetUserId 以及 Where(i => i.Item2.IsVisible(user))。GET /Playlists/{id} 却没有加上该过滤条件。ItemIds 来自 GetManageableItems(),对子项没有 IsVisible 检查。GetPlaylistForUser 只要求播放列表本身可见。
CreatePlaylistDto.IsPublic 默认为 true。OpenAccess 播放列表对其他用户是 IsVisible 的(HasParentalAllowedChild 仍允许第二个用户看到该播放列表)。未认证的 GET /Videos/{id}/stream 没有 [Authorize]。StreamingHelpers 加载 GetItemById 时不带用户上下文。
任何能看到公开播放列表的用户,都能从 ItemIds 中读取私有子项的 GUID,然后在没有令牌的情况下流式获取这些文件。
| ID | 暂无 CVE |
| 类别 | IDOR(播放列表 ItemIds 是 GHSA-9x85 的遗留问题,随后导致未认证流式访问) |
| 影响范围 | 远程(任何能看到公开播放列表的用户) |
| CWE | CWE-639、CWE-862 |
| CVSS | 高危:6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| 产品 | Jellyfin Server |
| 受影响版本 | v12.2(ca0f16eb)镜像 jellyfin/jellyfin:12.2 |
| 认证 | 获取播放列表需要认证;流式访问无需认证 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:18800 |
Jellyfin 12.2 遗留问题中的遗留问题 #3。遗留问题 #1(最重要):jellyfin-suggestions-idor。同一版本:jellyfin-svg-gzip · jellyfin-ffmpeg-colonslash。
受害者和攻击者是同一服务器上的两个用户,各自拥有互不相交的媒体库(例如儿童库与成人库,或一个隐藏文件夹)。受害者将一部私有影片放入公开播放列表。通过请求体创建时 IsPublic 默认为 true,因此这是常见情况,而非共享行为。攻击者以受限账户登录,读取播放列表对象,然后在没有 Authorization 头的情况下获取文件。
这泄露的是攻击者未被分配到的媒体库中媒体的 GUID,随后可通过流式接口获取完整文件。这不是播放列表标题枚举。添加/创建操作仍会丢弃不可见的条目。
| 方法 | 路径 | 权限 |
|---|---|---|
| GET | /Playlists/{playlistId} | 可见播放列表的子项 GUID(ItemIds 来自 GetManageableItems(),对子项没有 IsVisible 检查)。还包括 Shares[].UserId / CanEdit(共享接收者的 userId)。实验:B 的 GET /Playlists/{id}/Items 为空;同一 id 的 GET /Playlists/{id} 列出了私有 GUID a2bf95146e44897be6fb09020a82eb52。 |
GetPlaylistForUser 只要求播放列表本身可见。OpenAccess 加上 HasParentalAllowedChild(会跳过允许标签检查)就足够了。已修补的 Items 路由是对照组:它仍然会进行过滤。
| 方法 | 路径 | 权限 |
|---|---|---|
| GET | /Videos/{itemId}/stream?static=true | 私有媒体字节。没有 [Authorize]。 StreamingHelpers 在没有用户的情况下执行 GetItemById<BaseItem>(id)。实验:未认证返回 200,1142 字节,响应体中包含见证字符串 JELLYFIN-PLAYLIST-ITEMIDS-WITNESS。 |
| GET | /Audio/{itemId}/stream | 音频的同一接收点。相同的辅助方法,相同的无作用域查找。 |
已认证的 HLS GET /Videos/{itemId}/master.m3u8 走的是同一条 GetItemById 路径(DynamicHlsController 在类级别带有 [Authorize])。实验验证的是未认证的渐进式流,而非 HLS。
| 方法 | 路径 | 权限 |
|---|---|---|
| POST | /Playlists/{id}/Items 以及创建 Ids | 已关闭。Playlist.GetPlaylistItems 会执行 IsVisible。私有 GUID 会被丢弃,而不会被关联。 |
GetItemById(id, user))。EnableCollectionManagement 默认为 false;FilterLinkedChildrenPerUser)。ValidateTranscodePath 将旧版 HLS 分片保留在转码目录内)。userId 冒充(GetUserId 对外来 id 返回 403)。GET /Playlists/{id}/Items 列出私有子项(即已修补的路由;实验 items-filtered=yes)。没有密码/策略写入。没有插件安装。没有 shell。
在本实验中,仅靠文件夹 ACL(EnabledFolders)并未在 /Items 上隐藏子项。AllowedTags=["lab-public"] 使 IsVisible 将其丢弃。ItemIds 和未认证流不会查询该过滤器。
我固定了 jellyfin/jellyfin v12.2(ca0f16eb)。针对 12.0 GHSA 浪潮遗留问题进行了二十次源码排查。GHSA-9x85 让 GetPlaylistItems 过滤子项。我追问 GetPlaylist 是否也获得了同样的 Where。它并没有。流端点从未获得用户作用域的 GetItemById。
错误方向:将 EnabledFolders 视为 Items 的隐藏机制。影片的 IsVisible 是家长控制/标签,而非文件夹 ACL。实验必须设置 AllowedTags,才能让已修补的 Items 路由返回空。那是对照组。遗留问题并不使用它。
git clone https://github.com/abraxas/jellyfin-playlist-itemids
cd jellyfin-playlist-itemids
python3 jellyfin-playlist-itemids-Abraxas-Labs.py
或:
cd lab
./run.sh
仅针对 http://127.0.0.1:18800。镜像 jellyfin/jellyfin:12.2。run.sh 会生成两个极小的 mp4 文件(PrivateA 作为见证文件名,PublicB 作为占位文件)。向导会创建 labadmin、两个文件夹,然后创建 labuser,其配置为 EnableAllFolders=false、EnabledFolders=PublicB、AllowedTags=["lab-public"]。管理员将私有影片放入一个公开播放列表。仅限回环地址。仅用于见证。需要 Docker 和 ffmpeg 来生成种子文件。
SUCCESS JELLYFIN-PLAYLIST-ITEMIDS items-filtered=yes itemids-leak=yes unauth-stream=200 openAccess=yes playlist=e4798134497195ff4733dde62428e8d8 private=a2bf95146e44897be6fb09020a82eb52 witness-in-stream=yes witness-in-playlist-json=yes JELLYFIN-PLAYLIST-ITEMIDS-WITNESS
复现:python3 jellyfin-playlist-itemids-Abraxas-Labs.py。
使用 IsVisible(user) 过滤 ItemIds,与 GetPlaylistItems 保持一致。将流 GetItemById 限定到某个用户,或在 VideosController.GetVideoStream(以及对应的音频接口)上添加 [Authorize]。
ca0f16eb7c195f720a1493ed469a27c4657db0c6PlaylistsController.GetPlaylist(:163-182)· GetPlaylistItems(:524-541)Playlist.GetManageableItems · CreatePlaylistDto.IsPublicStreamingHelpers · VideosController.GetVideoStreamuserId IDORGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。