Trilium: Willkürlicher Dateizugriff und OOM-Absturz über `file://` in der Bild-URL einer Notiz

Ein authentifizierter Nutzer ohne Sonderrechte oder jeder Inhaber eines ETAPI-Tokens hinterlegt in einer Textnotiz eine Bild-URL mit dem Schema file://; der Server liest die Zieldatei ungeprüft und legt ihren Inhalt als herunterladbaren Anhang ab.

Advisory-ID: TP-2026-047
Produkt: Trilium (Open-Source-Notiz- und Wissensdatenbank-Anwendung mit Server- und Desktop-Komponente)
Schwachstellentyp: Willkürlicher Dateizugriff und Denial of Service (CWE-73)
CVE: CVE-2026-53580
CVSS 3.1: 8.1 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H
Hersteller-Advisory: GHSA-3gxr-j6g6-q75c
Betroffene Versionen: trilium < 0.104.0 (verifiziert auf v0.103.0, Docker)
Behoben in: 0.104.0
Gemeldet: 23. Mai 2026

Zusammenfassung

Trilium ist eine Open-Source-Notiz- und Wissensdatenbank-Anwendung; der Server lädt in Notizinhalten referenzierte Bilder automatisch herunter und speichert sie als Anhang. Der Bild-Downloader akzeptiert neben http(s) auch file://-URLs und reicht den Pfadteil ohne jede Prüfung an fs.readFile() weiter. Ein authentifizierter Nutzer ohne Sonderrechte oder jeder Inhaber eines ETAPI-Tokens hinterlegt daher in einer Textnotiz <img src="file:///etc/passwd">; der Server liest die Datei, legt die Bytes als Anhang ab und liefert sie über den Download-Endpunkt aus. So werden session_secret.txt, die komplette SQLite-Datenbank document.db und Konfigurationsdateien lesbar, was das Fälschen von Sitzungs-Cookies und das Offline-Entschlüsseln geschützter Notizen erlaubt; zeigt die URL auf /dev/zero, erschöpft der unbegrenzte Lesevorgang den Speicher und beendet den Prozess (OOM). turingpoint hat die Kette gegen eine laufende Trilium-Instanz live verifiziert (byte-genauer Abgleich per md5sum) und an den Hersteller gemeldet, der sie in 0.104.0 behoben hat.

Ursache

Der file://-Zweig von downloadImage() in apps/server/src/services/notes.ts:624-635 liest imageUrl.substring("file://".length) direkt mit fs.readFile(), ohne Kanonisierung, Allowlist, realpath-Prüfung oder Größenbegrenzung. Damit die URL überhaupt bis zum Sink überlebt, führt packages/commons/src/lib/shared_constants.ts:17 das Schema 'file' in ALLOWED_PROTOCOLS, sodass sanitize-html den <img src="file://…">-Tag unverändert durchlässt. Der Download wird bei jedem authentifizierten Speichern einer Textnotiz ausgelöst, weil die Option downloadImagesAutomatically in services/options_init.ts:161 standardmäßig auf true steht und downloadImages() (services/notes.ts:666-712) daraufhin jede <img>-Quelle abruft. Die geladenen Bytes werden über imageService.saveImageToAttachment() als Anhang gespeichert, auch wenn sharp sie nicht als Bild erkennt, und sind anschließend unter GET /api/attachments/:id/download (routes/api/files.ts) roh abrufbar. Der Pfad ist über PUT /api/notes/:noteId/data (Session + CSRF), PUT /etapi/notes/:noteId/content (Bearer-Token, ohne CSRF), den Web-Clipper und den ZIP-Import erreichbar und verlangt nur eine Authentifizierung ohne Administratorrechte.

Proof of Concept

# 1. ETAPI-Token beschaffen (Session + CSRF vorausgesetzt):
POST /api/etapi-tokens          {"tokenName":"poc"}

# 2. file://-URL in den Notizinhalt schreiben, der Download feuert automatisch:
PUT /etapi/notes/<noteId>/content
Authorization: <etapi-token>

<p><img src="file:///home/node/trilium-data/session_secret.txt"></p>

# 3. den erzeugten Anhang roh herunterladen:
GET  /api/notes/<noteId>/attachments            -> attachmentId
GET  /api/attachments/<attachmentId>/download   -> Dateiinhalt

Der Server liest den Pfad mit fs.readFile(), speichert die Bytes als Bildanhang (auch wenn es kein Bild ist) und gibt sie unverändert am Download-Endpunkt aus. Zeigt die src-URL statt auf eine Datei auf file:///dev/zero, läuft der Lesevorgang unbegrenzt und beendet den Node-Prozess durch Speichererschöpfung.

Auswirkung

  • Lesen jeder für den Node-Prozess (uid 1000 im Standard-Docker-Image) zugänglichen Datei, darunter session_secret.txt, document.db und Konfigurationsdateien.
  • Fälschen von Sitzungs-Cookies und CSRF-Token nach Exfiltration von session_secret.txt, wodurch eine Administrator-Sitzung offline übernommen wird.
  • Offline-Entschlüsselung geschützter Notizen und Offline-Brute-Force des Passworts über die aus document.db gewonnenen Hashes und Schlüssel.
  • Denial of Service durch unbegrenzte Speicherallokation über file:///dev/zero, die den Serverprozess per OOM beendet.

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.