
Jellyfin Server 12.2 - High - Remote - IDOR - GHSA-9x85에서 남은 공개 재생목록 ItemIds로 인한 비인증 스트리밍
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · jellyfin-playlist-itemids
분류: IDOR 도달 범위: 원격 (공개 재생목록을 볼 수 있는 모든 사용자)
Jellyfin Server v12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6) - Jellyfin
GHSA-9x85는 GetUserId와 Where(i => i.Item2.IsVisible(user))를 사용하여 GET /Playlists/{id}/Items를 패치했습니다. 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 (GHSA-9x85의 재생목록 ItemIds 잔여물, 이후 인증 없는 스트림) |
| 도달 범위 | 원격 (공개 재생목록을 볼 수 있는 모든 사용자) |
| 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 |
| 인증 | 재생목록 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.
피해자와 공격자는 하나의 서버에 있는 두 사용자이며 라이브러리가 분리되어 있습니다(어린이 vs 성인, 또는 숨겨진 폴더). 피해자가 공개 재생목록에 비공개 영화를 넣습니다. 본문 생성 시 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 물결의 잔여물에 대해 스무 차례 소스 탐색을 진행했다. 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는 두 개의 작은 mp4(PrivateA 증인 파일명, PublicB 더미)를 시드합니다. 마법사는 labadmin과 두 폴더를 생성한 뒤, EnableAllFolders=false, EnabledFolders=PublicB, AllowedTags=["lab-public"]로 labuser를 생성합니다. 관리자가 비공개 영화를 공개 재생목록에 넣습니다. 루프백. 증인 전용. 시드하려면 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와 일치하도록 IsVisible(user)로 ItemIds를 필터링합니다. 스트림 GetItemById의 범위를 사용자로 한정하거나, VideosController.GetVideoStream(및 오디오 관련 항목)에 [Authorize]를 적용합니다.
ca0f16eb7c195f720a1493ed469a27c4657db0c6PlaylistsController.GetPlaylist (:163-182) · GetPlaylistItems (:524-541)Playlist.GetManageableItems · CreatePlaylistDto.IsPublicStreamingHelpers · VideosController.GetVideoStreamuserId IDORGNU Affero GPL v3.0. LICENSE를 참조하세요. 루프백 랩 전용. 보증 없음.