Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
jellyfin-playlist-itemids — Jellyfin Server 12.2 - High - Remote - IDOR - public playlist ItemIds leftover of GHSA-9x85 then unauth stream | Kitploit
Tools/GitHubGitHub/abraxas/jellyfin-playlist-itemids
Vulnerability AnalysisWeb Application ExploitationData ExfiltrationInformation GatheringWeb SecurityPenetration TestingAPI SecurityLabs & Practice
GitHub
abraxas/jellyfin-playlist-itemids

jellyfin-playlist-itemids

Jellyfin Server 12.2 - High - Remote - IDOR - public playlist ItemIds leftover of GHSA-9x85 then unauth stream

View Repository
31 day agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Abraxas Labs - jellyfin-playlist-itemids

abraxaslabs.tech  ·  github.com/abraxas  ·  @abraxas_null  ·  [email protected]  ·  jellyfin-playlist-itemids

jellyfin-playlist-itemids

Class: IDOR Reach: Remote (any user who can see a public playlist)

Jellyfin Server v12.2 (ca0f16eb7c195f720a1493ed469a27c4657db0c6) - Jellyfin

GHSA-9x85 patched GET /Playlists/{id}/Items with GetUserId plus Where(i => i.Item2.IsVisible(user)). GET /Playlists/{id} was not given that filter. ItemIds is GetManageableItems() with no IsVisible on children. GetPlaylistForUser only requires playlist visibility.

CreatePlaylistDto.IsPublic defaults true. OpenAccess playlists are IsVisible to other users (HasParentalAllowedChild still lets a second user see the playlist). Unauth GET /Videos/{id}/stream has no [Authorize]. StreamingHelpers loads GetItemById without a user.

A user who can see a public playlist reads private child GUIDs from ItemIds, then streams the files with no token.

IDno CVE yet
ClassIDOR (playlist ItemIds leftover of GHSA-9x85, then unauth stream)
ReachRemote (any user who can see a public playlist)
CWECWE-639, CWE-862
CVSSHigh: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
ProductJellyfin Server
Affectedv12.2 (ca0f16eb) image jellyfin/jellyfin:12.2
Authauthenticated for the playlist GET; none for the stream
LicenseGNU Affero GPL v3.0
Lab127.0.0.1:18800 only

Leftover #3 of Jellyfin 12.2 leftovers. Leftover #1 (most important): jellyfin-suggestions-idor. Same pin: jellyfin-svg-gzip · jellyfin-ffmpeg-colonslash.

What an attacker can do

Victim and attacker are two users on one server with disjoint libraries (kids vs adults, or a hidden folder). Victim puts a private movie on a public playlist. Body-create IsPublic defaults true, so that is the usual case, not a share. Attacker logs in as a restricted account, reads the playlist object, then fetches the file with no Authorization header.

This is GUIDs of media in libraries the attacker is not assigned, then the full file via stream. It is not playlist-title enum. Add/create still drop non-visible items.

Recon (lab-proven)

MethodPathPrivilege
GET/Playlists/{playlistId}Child GUIDs of a visible playlist (ItemIds from GetManageableItems(), no IsVisible on children). Also Shares[].UserId / CanEdit (sharee userIds). Lab: B's GET /Playlists/{id}/Items empty; same id's GET /Playlists/{id} listed private GUID a2bf95146e44897be6fb09020a82eb52.

GetPlaylistForUser only requires the playlist itself to be visible. OpenAccess plus HasParentalAllowedChild (which skips the allowed-tag check) is enough. The patched Items route is the control: it still filters.

Confidentiality (lab-proven)

MethodPathPrivilege
GET/Videos/{itemId}/stream?static=truePrivate media bytes. No [Authorize]. StreamingHelpers does GetItemById<BaseItem>(id) without a user. Lab: unauth 200, 1142 bytes, witness JELLYFIN-PLAYLIST-ITEMIDS-WITNESS in the body.
GET/Audio/{itemId}/streamSame sink for audio. Same helper, same unscoped lookup.

Authenticated HLS GET /Videos/{itemId}/master.m3u8 is the same GetItemById path (DynamicHlsController has class-level [Authorize]). Lab exercised the unauth progressive stream, not HLS.

Integrity (closed)

MethodPathPrivilege
POST/Playlists/{id}/Items and create IdsClosed. Playlist.GetPlaylistItems runs IsVisible. Private GUIDs are dropped, not linked.

Adjacent surface that stays closed

  • Adding private GUIDs to your own playlist.
  • InstantMix (user-scoped GetItemById(id, user)).
  • Collections (EnableCollectionManagement default false; FilterLinkedChildrenPerUser).
  • GHSA-hw73 transcode prefix (ValidateTranscodePath keeps legacy HLS segments inside the transcode dir).
  • GHSA-9x85 userId impersonation on Items (GetUserId 403s a foreign id).
  • GET /Playlists/{id}/Items listing of private children (the patched route; lab items-filtered=yes).

No password/policy write. No plugin install. No shell.

Folder ACL (EnabledFolders) alone did not hide the child on /Items in this lab. AllowedTags=["lab-public"] made IsVisible drop it. ItemIds and unauth stream do not consult that filter.

How I found it

I pinned jellyfin/jellyfin v12.2 (ca0f16eb). Twenty source hunts on leftovers of the 12.0 GHSA wave. GHSA-9x85 taught GetPlaylistItems to filter children. I asked whether GetPlaylist got the same Where. It did not. Stream endpoints never received a user-scoped GetItemById.

Wrong turn: treating EnabledFolders as the Items hide. Movie IsVisible is parental/tags, not folder ACL. The lab had to set AllowedTags so the patched Items route would go empty. That is the control. The leftover does not use it.

Lab

git clone https://github.com/abraxas/jellyfin-playlist-itemids
cd jellyfin-playlist-itemids
python3 jellyfin-playlist-itemids-Abraxas-Labs.py

Or:

cd lab
./run.sh

Target only http://127.0.0.1:18800. Image jellyfin/jellyfin:12.2. run.sh seeds two tiny mp4s (PrivateA witness filename, PublicB dummy). Wizard creates labadmin, both folders, then labuser with EnableAllFolders=false, EnabledFolders=PublicB, AllowedTags=["lab-public"]. Admin puts the private movie on a public playlist. Loopback. Witness-only. Requires Docker and ffmpeg to seed.

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

Replay: python3 jellyfin-playlist-itemids-Abraxas-Labs.py.

Fix

Filter ItemIds with IsVisible(user), matching GetPlaylistItems. Scope stream GetItemById to a user, or put [Authorize] on VideosController.GetVideoStream (and the audio sibling).

References

Download Tool