Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
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.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
zotlit-PoC — PoC — attachment import copies files from unapproved local paths in ZotLit (GHSA-4qh7-66xv-h329, CVE-2026-87000, CVSS 5.5). | Kitploit
Tools/GitHubGitHub/squeeze440/zotlit-poc
Vulnerability AnalysisExploitationData ExfiltrationInformation GatheringSecurity VirtualizationPapers & Research
GitHubsqueeze440/zotlit-poc

zotlit-PoC

PoC — attachment import copies files from unapproved local paths in ZotLit (GHSA-4qh7-66xv-h329, CVE-2026-87000, CVSS 5.5).

View Repository
2019 days 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

ZotLit: security advisory

CVE status: requested, pending assignment. This finding is published as GHSA-4qh7-66xv-h329. On CVE assignment this repository is renamed CVE-YYYY-NNNNN-zotlit-PoC and this banner is replaced with the CVE link.

ResearcherDostxodjayev Abdullox (@squeeze440)
AdvisoryGHSA-4qh7-66xv-h329
CVSS 3.15.5 (Medium)
WeaknessCWE-73, CWE-200

Summary

External Control of File Name or Path in the attachment-import feature in AidenLx ZotLit (aidenlx/zotlit) 1.1.12 allows an attacker who controls a shared/synced Zotero library to disclose arbitrary local files from the victim's filesystem into the victim's Obsidian vault via a crafted linked_file attachment path.

Product

ZotLit — Obsidian plugin for Zotero integration (aidenlx/zotlit, plugin id zotlit, manifest version 1.1.12)

Tested Version

Git commit 41e60aaa6178629b34f8a42d104e757594b72435 (2026-07-31)

Estimated CVSS v3.1

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N — 5.5 (Medium)

Non-obvious metrics: AV:L — exploitation occurs when the victim's local Obsidian/zotlit process processes attacker-supplied Zotero item metadata (delivered via a shared/synced library), the same convention used for "malicious file processed by local app" bugs, even though delivery itself can happen over a network (shared group library, emailed export file). UI:R — the victim must import/sync the malicious library into Zotero and run ZotLit's note-import (or annotation/citation embed) feature on a note referencing the crafted attachment; this is routine use of the plugin's core, default-enabled functionality (attachment.import defaults to true), not an unusual action. I:N/A:N — this finding is a read/copy primitive only; no destination-side path traversal is claimed (see Details for a related but unverified observation).

Details

ZotLit reads attachment metadata straight from Zotero's SQLite database (or a synced/shared library), including the free-text itemAttachments.path column, and trusts it completely when resolving where to read a "linked" attachment from:

  • packages/db/src/lib/zt-path.ts:62-63 — attachmentAbsPath(), case "linked-absolute":

    case "linked-absolute":
      return parsed.path;
    

    For linkMode: 2 (linked_file) rows whose path doesn't carry the attachments: base-dir placeholder, parsed.path is the raw DB string returned verbatim as the absolute filesystem path to read — no allowlist, no confinement to the Zotero data directory or any user-approved location.

  • packages/db/src/lib/context/zt-template-attach.ts:101-106 — attachmentFilename(), case "linked-absolute":

    case "linked-absolute":
      return basename(path.path);
    

    The filename used to build the in-vault copy is derived from that same attacker-controlled path via basename(), so the destination filename is attacker-influenced but path-separator-free (traversal-safe on this branch).

  • apps/obsidian/src/services/note-import/note-parser.ts:403-430 — resolveEmbeddedImage() is invoked while converting a Zotero literature-note's embedded image annotation into Obsidian Markdown (a routine, default action of ZotLit's "Import Note" feature). It resolves sourcePath = attachmentAbsPath(attachment, ...) and calls deps.resolveLink({ sourcePath, vaultName: \${attachment.key}-${filename}` })` — queuing a copy of any attacker-chosen absolute path into the vault.

  • apps/obsidian/src/services/attachment-import/service.ts:125-155 — AttachmentImportBatch.resolveLink() queues { source: sourcePath, dest: <vault attachment folder>/<key>-<filename> }, gated only by the attachment.import setting, whose default is true (apps/obsidian/src/services/settings/schema.ts:123).

  • apps/obsidian/src/lib/copy-attachments.ts:43-60 — copyAttachment() performs the actual read+write with no path validation: stat(source) then copyFile(source, dest).

Net effect: an attacker who can get a linked_file (linkMode 2) Zotero item into the victim's library — e.g. a shared Zotero group library, a .rdf/.json/Better BibTeX export the victim imports, or a synced library the attacker has write access to — can set that item's attachment path to any file on the victim's disk (~/.ssh/id_rsa, browser credential stores, other vaults, .env files, etc.). The moment the victim runs ZotLit's note-import (or renders/embeds that annotation) with the default attachment.import: true setting, ZotLit silently copies that file's content into the victim's Obsidian vault under a predictable name (<attachmentKey>-<basename>). Because vaults are routinely synced, git-committed, or published, this moves data the attacker could never otherwise reach into a location the attacker (or anyone else with access to the sync target) can read.

Related, unverified observation (not part of this finding's PoC): the sibling "storage" branch of attachmentFilename() (zt-template-attach.ts:103-104) returns the raw path suffix without the basename() call applied to the other two branches. Whether this is independently exploitable depends on how Zotero's own storage-sync mechanism names locally-downloaded files, which is outside this repo and was not verified — flagged here only as a hardening gap worth closing for consistency.

Proof of Concept

Dynamic verification: the exact vulnerable functions (parseAttachmentPath, attachmentAbsPath, attachmentFilename, copyAttachments/copyAttachment/writeCopy/destMatches, isErrno, reflink) were extracted verbatim (file:line-cited above, logic unchanged) from the shipped source and executed directly in Node, because a full offline pnpm workspace install was not available in this sandbox (corepack/pnpm toolchain broken offline). The only substitution was swapping the LogTape logger call for console.warn — cosmetic only, zero effect on control flow. Script: ~/engagements/zotlit/evidence/poc-verify.mjs.

Steps:

  1. Simulated victim secret at ~/engagements/zotlit/evidence/victim-disk/id_rsa (dummy content, clearly marked simulated).
  2. Crafted attacker-controlled Zotero attachment row: { key: "EVILKEY1", path: "<path to victim's id_rsa>", linkMode: 2 } — exactly the shape getAttachmentByKey returns from Zotero's DB.
  3. Ran the real attachmentAbsPath() — resolved the source as the victim's file, verbatim, no validation.
  4. Ran the real attachmentFilename() — resolved id_rsa as the safe-looking destination basename.
  5. Reproduced the real vaultName construction from note-parser.ts:427 (${key}-${filename}).
  6. Ran the real copyAttachments() — result { copied: 1, skipped: 0, missing: 0 }.
  7. Read back fake-vault/attachments/EVILKEY1-id_rsa — content byte-for-byte matches the victim's original file.

Screenshot of the full run (command + output) in a real xterm: ../evidence/poc-run.png

Impact

An attacker who can influence a victim's Zotero library contents (shared/group library, imported bibliography export, or synced library) can exfiltrate arbitrary local files the victim's OS user can read — SSH keys, credential stores, other vault contents, .env/config secrets — into the victim's Obsidian vault, with no prompt or confirmation, the moment the victim uses ZotLit's core note-import functionality with default settings. Vaults are commonly synced (Obsidian Sync, git, cloud folders) or published, so this converts a local-file-read primitive into realistic remote exposure.

Weaknesses

  • CWE-73: External Control of File Name or Path
  • CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

Remediation

Download Tool