seerr: Path Traversal zu Remote Code Execution über den Cache-Dateinamen des Avatar-Proxys
Ein bösartiger oder kompromittierter Jellyfin-/Emby-Medienserver liefert über den Avatar-Proxy einen ETag-Header mit Traversal-Sequenz, überschreibt damit den Anwendungs-Einstiegspunkt und führt beim nächsten Neustart Code als Nutzer node aus.
Advisory-ID: TP-2026-041
Produkt: seerr (Selbst-gehostete Open-Source-Anwendung zur Verwaltung von Medienanfragen für Jellyfin-, Emby- und Plex-Medienserver)
Schwachstellentyp: Path Traversal zu Remote Code Execution (CWE-22)
CVE: CVE-2026-73291
CVSS 3.1: 7.1 (Hoch) · CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:H
Hersteller-Advisory: GHSA-mc6w-69r3-62h8
Betroffene Versionen: < 3.4.0
Behoben in: 3.4.0
Gemeldet: 2. Juli 2026
Zusammenfassung
seerr ist eine selbst-gehostete Anwendung, die Medienanfragen für angebundene Jellyfin-, Emby- und Plex-Server verwaltet. Der Avatar-Proxy legt heruntergeladene Avatare in einem Cache ab und bildet den Dateinamen dieses Caches aus dem ETag-Antwortheader des Medienservers, ohne Pfadtrenner zu entfernen. Da path.join die Sequenz ../ zusammenfasst und die Dateiendung aus dem Content-Type des Medienservers stammt, kann ein Angreifer den Schreibpfad aus dem Cache-Verzeichnis herausführen und die Datei /app/dist/index.js überschreiben, den von npm start ausgeführten Einstiegspunkt. Der auslösende Endpunkt ist unauthentifiziert, und Angreifer kontrollieren sowohl den Zielpfad als auch den geschriebenen Inhalt, sodass beim nächsten Neustart des Containers beliebiger Code als Nutzer node läuft. Voraussetzung ist die Kontrolle über die Antwort des Medienservers, also ein bösartiger oder kompromittierter Server oder eine On-Path-Position auf der standardmäßig unverschlüsselten Verbindung. turingpoint hat die Kette Ende-zu-Ende live verifiziert und an den Hersteller gemeldet, der sie in 3.4.0 behoben hat.
Ursache
Der Avatar-Proxy bildet den Cache-Dateinamen ${maxAge}.${expireAt}.${etag}.${extension} und übergibt ihn an path.join und fs.writeFile (server/lib/imageproxy.ts:321, :328), wobei path.join die Sequenz ../ zusammenfasst und der Pfad so das Cache-Verzeichnis verlassen kann. Das Segment etag stammt unmittelbar aus dem ETag-Antwortheader des Medienservers und es werden nur doppelte Anführungszeichen entfernt (server/lib/imageproxy.ts:281), während die Dateiendung über mime.getExtension aus dem Content-Type des Medienservers abgeleitet wird (:273), sodass text/javascript zu js wird. Ein ETag von "/../../../../../../dist/index" mit Content-Type: text/javascript schreibt daher nach /app/dist/index.js, dem Einstiegspunkt, den npm start als node dist/index.js ausführt, wodurch sowohl der Schreibpfad als auch die geschriebenen Bytes vom Angreifer bestimmt werden. Der Schreibvorgang wird über die unauthentifizierte Route GET /avatarproxy/:jellyfinUserId (server/index.ts:252, keine Auth-Middleware) für einen Nutzer erreicht, dessen ID ^[a-f0-9]{32}$ erfüllt und der eine Avatar-Version gesetzt hat; der Avatar wird vom konfigurierten Medienserver über getHostname() geladen (avatarproxy.ts:44). Die Voraussetzung ist die Kontrolle über die Antwort des Medienservers, also ein bösartiger oder kompromittierter Server oder eine On-Path-Position auf der standardmäßig unverschlüsselten Verbindung (server/lib/settings/index.ts:436, useSsl:false).
Proof of Concept
# Der vom Angreifer kontrollierte Medienserver antwortet auf den Avatar-Abruf mit:
HTTP/1.1 200 OK
ETag: "/../../../../../../dist/index"
Content-Type: text/javascript
<Angreifer-JavaScript>
# Ein einziger unauthentifizierter Request loest den zwischengespeicherten Schreibvorgang aus:
GET /avatarproxy/<32-stellige-hex-jellyfinUserId>
path.join fasst die Sequenz ../ zusammen, sodass die Datei nach /app/dist/index.js geschrieben wird; beim nächsten Neustart des Containers führt npm start über node dist/index.js das eingeschleuste JavaScript aus. turingpoint hat dies live auf ghcr.io/seerr-team/seerr:latest (v3.3.0) bestätigt: Ein unauthentifizierter GET /avatarproxy/<32hex> überschrieb /app/dist/index.js, und nach docker restart lief das Payload als uid=1000(node), schrieb /tmp/RCE_PROOF und sandte einen Netzwerk-Beacon an den Angreifer-Host.
Auswirkung
- Überschreiben beliebiger Dateien im Container, einschließlich des Anwendungs-Einstiegspunkts
/app/dist/index.js. - Remote Code Execution als Nutzer
node(uid 1000) beim nächsten Neustart des Containers. - Manipulation der SQLite-Datenbank und der Anwendungskonfiguration auf der Festplatte.
- Keine Authentifizierung für den auslösenden Request nötig; Voraussetzung ist die Kontrolle über die Antwort des Medienservers.
Referenzen
Steckt so etwas in Ihrer Software?
Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.
