
Jellyfin Server 12.2 - 高 - リモート - IDOR - 公開プレイリストのItemIdsがGHSA-9x85の残存物となり、その後未認証ストリームが可能
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 により、2人目のユーザーもプレイリストを閲覧できます)。未認証の GET /Videos/{id}/stream には [Authorize] がありません。StreamingHelpers はユーザーなしで GetItemById を読み込みます。
公開プレイリストを閲覧できるユーザーは、ItemIds からプライベートな子要素の GUID を読み取り、トークンなしでファイルをストリーミングできます。
| ID | CVE 未割当 |
| クラス | IDOR(GHSA-9x85 の残存であるプレイリスト ItemIds、続いて未認証ストリーム) |
| 到達範囲 | リモート(公開プレイリストを閲覧できる任意のユーザー) |
| CWE | CWE-639, CWE-862 |
| CVSS | High: 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 |
| 認証 | プレイリスト GET には認証が必要、ストリームには不要 |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1:18800 のみ |
Jellyfin 12.2 leftovers の残存 #3。残存 #1(最重要): jellyfin-suggestions-idor。同じピン: jellyfin-svg-gzip · jellyfin-ffmpeg-colonslash。
被害者と攻撃者は、ライブラリが重複しない(子供向けと大人向け、または隠しフォルダ)同一サーバー上の2ユーザーです。被害者がプライベートな映画を公開プレイリストに追加します。ボディ作成時の IsPublic はデフォルトで true であるため、これは共有ではなく通常のケースです。攻撃者は制限付きアカウントでログインし、プレイリストオブジェクトを読み取り、Authorization ヘッダーなしでファイルを取得します。
これは攻撃者が割り当てられていないライブラリ内のメディアの GUID、そしてストリーム経由の完全なファイルです。プレイリストタイトルの列挙ではありません。追加/作成は依然として非表示アイテムを除外します。
| メソッド | パス | 権限 |
|---|---|---|
| GET | /Playlists/{playlistId} | 可視プレイリストの子 GUID(GetManageableItems() からの ItemIds、子要素に 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 バイト、本文に witness 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)。パスワード/ポリシーの書き込みなし。プラグインのインストールなし。シェルなし。
このラボでは、フォルダ ACL(EnabledFolders)だけでは /Items 上の子要素を隠せなかった。AllowedTags=["lab-public"] を設定すると IsVisible がそれを破棄した。ItemIds と未認証ストリームはそのフィルターを参照しない。
私は jellyfin/jellyfin v12.2(ca0f16eb)を固定した。12.0 の GHSA 波の残骸に対して 20 回のソース探索を行った。GHSA-9x85 は GetPlaylistItems に子要素をフィルタリングすることを教えた。私は GetPlaylist にも同じ Where が付いたのかを問うた。付いていなかった。ストリームエンドポイントはユーザースコープの GetItemById を決して受け取らなかった。
誤った方向: EnabledFolders を Items の隠蔽とみなしたこと。ムービーの IsVisible はペアレンタル/タグによるものであり、フォルダ ACL ではない。ラボでは、パッチ済みの Items ルートが空になるように AllowedTags を設定する必要があった。それが対照である。残骸はそれを使用しない。
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 は2つの小さな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。
GetPlaylistItems に合わせて、ItemIds を IsVisible(user) でフィルタリングします。ストリーム GetItemById をユーザーにスコープするか、VideosController.GetVideoStream(および対応する音声側)に [Authorize] を付与します。
ca0f16eb7c195f720a1493ed469a27c4657db0c6PlaylistsController.GetPlaylist (:163-182) · GetPlaylistItems (:524-541)Playlist.GetManageableItems · CreatePlaylistDto.IsPublicStreamingHelpers · VideosController.GetVideoStreamuserId IDORGNU Affero GPL v3.0。LICENSE を参照してください。ループバックラボ専用です。保証はありません。