
PoC — Anhang-Import kopiert Dateien aus nicht genehmigten lokalen Pfaden in ZotLit (GHSA-4qh7-66xv-h329, CVE-2026-87000, CVSS 5.5).
CVE-Status: angefragt, Zuweisung ausstehend. Dieser Fund wird als GHSA-4qh7-66xv-h329 veröffentlicht. Bei CVE-Zuweisung wird dieses Repository in
CVE-YYYY-NNNNN-zotlit-PoCumbenannt und dieses Banner durch den CVE-Link ersetzt.
| Forscher | Dostxodjayev Abdullox (@squeeze440) |
| Advisory | GHSA-4qh7-66xv-h329 |
| CVSS 3.1 | 5.5 (Mittel) |
| Schwachstelle | CWE-73, CWE-200 |
Zusammenfassung
Externe Kontrolle über Dateinamen oder Pfad in der Attachment-Import-Funktion in AidenLx ZotLit (aidenlx/zotlit) 1.1.12 ermöglicht einem Angreifer, der eine gemeinsam genutzte/synchronisierte Zotero-Bibliothek kontrolliert, die Offenlegung beliebiger lokaler Dateien aus dem Dateisystem des Opfers in den Obsidian-Vault des Opfers über einen manipulierten linked_file-Attachment-Pfad.
Produkt
ZotLit — Obsidian-Plugin für die Zotero-Integration (aidenlx/zotlit, Plugin-ID zotlit, Manifest-Version 1.1.12)
Getestete Version
Git-Commit 41e60aaa6178629b34f8a42d104e757594b72435 (2026-07-31)
Geschätzte CVSS v3.1
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N — 5.5 (Mittel)
Nicht offensichtliche Metriken: AV:L — die Ausnutzung erfolgt, wenn der lokale Obsidian/zotlit-Prozess des Opfers vom Angreifer bereitgestellte Zotero-Item-Metadaten verarbeitet (übermittelt über eine gemeinsam genutzte/synchronisierte Bibliothek), dieselbe Konvention wie bei Fehlern vom Typ „bösartige Datei wird von lokaler App verarbeitet“, obwohl die Übermittlung selbst über ein Netzwerk erfolgen kann (gemeinsame Gruppenbibliothek, per E-Mail versandte Exportdatei). UI:R — das Opfer muss die bösartige Bibliothek in Zotero importieren/synchronisieren und ZotLits Notiz-Import-Funktion (oder das Einbetten von Annotationen/Zitaten) auf eine Notiz anwenden, die auf das manipulierte Attachment verweist; dies ist die routinemäßige Nutzung der Kernfunktionalität des Plugins, die standardmäßig aktiviert ist (attachment.import ist standardmäßig true), und keine ungewöhnliche Aktion. I:N/A:N — dieser Fund ist lediglich ein Lese-/Kopier-Primitiv; es wird kein Path Traversal auf der Zielseite behauptet (siehe Details für eine verwandte, aber unbestätigte Beobachtung).
Details
ZotLit liest Attachment-Metadaten direkt aus Zoteros SQLite-Datenbank (oder einer synchronisierten/gemeinsam genutzten Bibliothek), einschließlich der Freitext-Spalte itemAttachments.path, und vertraut ihr vollständig, wenn es auflöst, woher ein „verlinktes“ Attachment gelesen werden soll:
packages/db/src/lib/zt-path.ts:62-63 — attachmentAbsPath(), Fall "linked-absolute":
case "linked-absolute":
return parsed.path;
Bei linkMode: 2 (linked_file)-Zeilen, deren path nicht den Platzhalter attachments: für das Basisverzeichnis enthält, ist parsed.path der rohe DB-String, der unverändert als absoluter Dateisystempfad zum Lesen zurückgegeben wird — keine Allowlist, keine Beschränkung auf das Zotero-Datenverzeichnis oder einen vom Benutzer genehmigten Ort.
packages/db/src/lib/context/zt-template-attach.ts:101-106 — attachmentFilename(), Fall "linked-absolute":
case "linked-absolute":
return basename(path.path);
Der Dateiname, der zum Erstellen der Kopie im Vault verwendet wird, wird aus demselben vom Angreifer kontrollierten Pfad über basename() abgeleitet, sodass der Zieldateiname vom Angreifer beeinflusst, aber frei von Pfadtrennzeichen ist (in diesem Zweig traversal-sicher).
apps/obsidian/src/services/note-import/note-parser.ts:403-430 — resolveEmbeddedImage() wird aufgerufen, während eine eingebettete Bildannotation einer Zotero-Literatur-Notiz in Obsidian-Markdown umgewandelt wird (eine routinemäßige, standardmäßige Aktion der ZotLit-Funktion „Import Note“). Es löst sourcePath = attachmentAbsPath(attachment, ...) auf und ruft deps.resolveLink({ sourcePath, vaultName: \${attachment.key}-${filename}` })` auf — wodurch eine Kopie jedes vom Angreifer gewählten absoluten Pfads in den Vault eingereiht wird.
apps/obsidian/src/services/attachment-import/service.ts:125-155 — AttachmentImportBatch.resolveLink() reiht { source: sourcePath, dest: <vault attachment folder>/<key>-<filename> } ein, nur gesteuert durch die Einstellung attachment.import, deren Standardwert true ist (apps/obsidian/src/services/settings/schema.ts:123).
apps/obsidian/src/lib/copy-attachments.ts:43-60 — copyAttachment() führt das eigentliche Lesen+Schreiben ohne Pfadvalidierung durch: stat(source) und dann copyFile(source, dest).
Nettoeffekt: Ein Angreifer, der ein linked_file (linkMode 2)-Zotero-Item in die Bibliothek des Opfers bringen kann — z. B. eine gemeinsam genutzte Zotero-Gruppenbibliothek, ein .rdf/.json/Better BibTeX-Export, den das Opfer importiert, oder eine synchronisierte Bibliothek, auf die der Angreifer Schreibzugriff hat — kann den Attachment-Pfad dieses Items auf jede Datei auf der Festplatte des Opfers setzen (~/.ssh/id_rsa, Browser-Anmeldeinformationsspeicher, andere Vaults, .env-Dateien usw.). In dem Moment, in dem das Opfer ZotLits Notiz-Import ausführt (oder diese Annotation rendert/einbettet) mit der Standardeinstellung attachment.import: true, kopiert ZotLit stillschweigend den Inhalt dieser Datei in den Obsidian-Vault des Opfers unter einem vorhersehbaren Namen (<attachmentKey>-<basename>). Da Vaults routinemäßig synchronisiert, in Git committet oder veröffentlicht werden, verschiebt dies Daten, die der Angreifer sonst nie erreichen könnte, an einen Ort, den der Angreifer (oder jeder andere mit Zugriff auf das Synchronisierungsziel) lesen kann.
Verwandte, unbestätigte Beobachtung (nicht Teil des PoC dieses Funds): Der benachbarte "storage"-Zweig von attachmentFilename() (zt-template-attach.ts:103-104) gibt das rohe Pfadsuffix zurück, ohne dass der basename()-Aufruf angewendet wird, der auf die anderen beiden Zweige angewendet wird. Ob dies unabhängig ausnutzbar ist, hängt davon ab, wie Zoteros eigener Storage-Sync-Mechanismus lokal heruntergeladene Dateien benennt, was außerhalb dieses Repos liegt und nicht verifiziert wurde — hier nur als Härtungslücke gekennzeichnet, die aus Konsistenzgründen geschlossen werden sollte.
Proof of Concept
Dynamische Verifikation: Die exakt verwundbaren Funktionen (parseAttachmentPath, attachmentAbsPath, attachmentFilename, copyAttachments/copyAttachment/writeCopy/destMatches, isErrno, reflink) wurden wörtlich (oben mit Datei:Zeile zitiert, Logik unverändert) aus dem ausgelieferten Quellcode extrahiert und direkt in Node ausgeführt, da eine vollständige Offline-pnpm-Workspace-Installation in dieser Sandbox nicht verfügbar war (die corepack/pnpm-Toolchain ist offline defekt). Die einzige Ersetzung war der Austausch des LogTape-Logger-Aufrufs durch console.warn — rein kosmetisch, ohne Auswirkung auf den Kontrollfluss. Skript: ~/engagements/zotlit/evidence/poc-verify.mjs.
Schritte:
~/engagements/zotlit/evidence/victim-disk/id_rsa (Dummy-Inhalt, klar als simuliert gekennzeichnet).{ key: "EVILKEY1", path: "<path to victim's id_rsa>", linkMode: 2 } — genau die Form, die getAttachmentByKey aus Zoteros DB zurückgibt.attachmentAbsPath() ausgeführt — löste die Quelle als Datei des Opfers auf, wörtlich, ohne Validierung.attachmentFilename() ausgeführt — löste id_rsa als sicher wirkenden Ziel-Basename auf.vaultName-Konstruktion aus note-parser.ts:427 (${key}-${filename}) reproduziert.copyAttachments() ausgeführt — Ergebnis { copied: 1, skipped: 0, missing: 0 }.fake-vault/attachments/EVILKEY1-id_rsa zurückgelesen — Inhalt stimmt Byte für Byte mit der Originaldatei des Opfers überein.Screenshot des vollständigen Durchlaufs (Befehl + Ausgabe) in einem echten xterm: ../evidence/poc-run.png
Auswirkung
Ein Angreifer, der den Inhalt der Zotero-Bibliothek eines Opfers beeinflussen kann (gemeinsam genutzte/Gruppenbibliothek, importierter Bibliografie-Export oder synchronisierte Bibliothek), kann beliebige lokale Dateien exfiltrieren, die der OS-Benutzer des Opfers lesen kann — SSH-Schlüssel, Anmeldeinformationsspeicher, Inhalte anderer Vaults, .env/Konfigurationsgeheimnisse — in den Obsidian-Vault des Opfers, ohne Aufforderung oder Bestätigung, in dem Moment, in dem das Opfer ZotLits Kernfunktion zum Notiz-Import mit Standardeinstellungen verwendet. Vaults werden häufig synchronisiert (Obsidian Sync, Git, Cloud-Ordner) oder veröffentlicht, sodass dies ein lokales Datei-Lese-Primitiv in eine realistische Remote-Exposition verwandelt.
Schwachstellen
Behebung
Es wird empfohlen, linked_file (linkMode 2)-Attachment-Quellen auf Dateisystemorte zu beschränken, die das Opfer explizit konfiguriert oder genehmigt hat (z. B. Zoteros eigenen konfigurierten baseAttachmentPath oder das Zotero-Datenverzeichnis), und den Benutzer aufzufordern, bevor ein verlinktes Attachment automatisch kopiert wird, dessen Pfad außerhalb eines erwarteten Wurzelverzeichnisses liegt. Als Defense in Depth sollte in Betracht gezogen werden, dieselbe basename()-only-Bereinigung, die bereits auf die Zweige linked-absolute/linked-base von attachmentFilename() angewendet wird, auch auf den Zweig "storage" anzuwenden, damit kein Codepfad ein unbereinigtes Dateinamenfragment zurückgibt.
Danksagung
Dostxodjayev Abdullox (GitHub: squeeze440)
Meldeweg
Private Vulnerability Reporting ist auf aidenlx/zotlit aktiviert — melden Sie über einen GitHub Security Advisory (GHSA)-Entwurf im Repository statt über ein öffentliches Issue.