CVEステータス: リクエスト済み、割り当て待ち。この脆弱性は GHSA-4qh7-66xv-h329 として公開されています。CVEが割り当てられ次第、このリポジトリは
CVE-YYYY-NNNNN-zotlit-PoCに改名され、このバナーはCVEリンクに置き換えられます。
| 研究者 | Dostxodjayev Abdullox (@squeeze440) |
| アドバイザリ | GHSA-4qh7-66xv-h329 |
| CVSS 3.1 | 5.5 (Medium) |
| 脆弱性 | CWE-73, CWE-200 |
概要
AidenLx ZotLit (aidenlx/zotlit) 1.1.12 の添付ファイルインポート機能におけるファイル名またはパスの外部制御により、共有/同期されたZoteroライブラリを制御する攻撃者が、細工された linked_file 添付パスを介して、被害者のファイルシステムから任意のローカルファイルを被害者のObsidian vaultに開示させることができます。
製品
ZotLit — Zotero統合用のObsidianプラグイン (aidenlx/zotlit、プラグインID zotlit、マニフェストバージョン 1.1.12)
テスト済みバージョン
Gitコミット 41e60aaa6178629b34f8a42d104e757594b72435 (2026-07-31)
推定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)
自明でない指標: AV:L — 攻撃は、被害者のローカルObsidian/zotlitプロセスが攻撃者提供のZoteroアイテムメタデータ(共有/同期ライブラリ経由で配信)を処理する際に発生します。これは「ローカルアプリによって処理される悪意あるファイル」のバグで使われるのと同じ慣例であり、配信自体はネットワーク経由(共有グループライブラリ、メール添付のエクスポートファイル)で行われる可能性があります。UI:R — 被害者が悪意あるライブラリをZoteroにインポート/同期し、細工された添付ファイルを参照するノートに対してZotLitのノートインポート(または注釈/引用の埋め込み)機能を実行する必要があります。これはプラグインの中核となるデフォルト有効機能(attachment.import のデフォルトは true)の日常的な使用であり、異常な操作ではありません。I:N/A:N — この脆弱性は読み取り/コピーのプリミティブに過ぎません。宛先側のパストラバーサルは主張していません(関連するが未検証の観察については「詳細」を参照)。
詳細
ZotLitは添付ファイルのメタデータをZoteroのSQLiteデータベース(または同期/共有ライブラリ)から直接読み取り、自由記述の itemAttachments.path カラムを含めて、リンクされた添付ファイルをどこから読み取るかを解決する際にそれを完全に信頼します:
packages/db/src/lib/zt-path.ts:62-63 — attachmentAbsPath()、"linked-absolute" ケース:
case "linked-absolute":
return parsed.path;
linkMode: 2 (linked_file) の行で、その path に attachments: ベースディレクトリのプレースホルダが含まれていない場合、parsed.path はDBの生の文字列がそのまま読み取り対象の絶対ファイルシステムパスとして返されます — 許可リストもなく、Zoteroデータディレクトリやユーザー承認済みの場所への制限もありません。
packages/db/src/lib/context/zt-template-attach.ts:101-106 — attachmentFilename()、"linked-absolute" ケース:
case "linked-absolute":
return basename(path.path);
vault内コピーを構築するために使用されるファイル名は、同じ攻撃者制御のパスから basename() を介して導出されるため、宛先ファイル名は攻撃者の影響を受けますが、パス区切り文字は含まれません(この分岐ではトラバーサル安全)。
apps/obsidian/src/services/note-import/note-parser.ts:403-430 — resolveEmbeddedImage() は、Zoteroの文献ノートに埋め込まれた画像注釈をObsidian Markdownに変換する際に呼び出されます(ZotLitの「Import Note」機能の日常的でデフォルトの動作)。これは sourcePath = attachmentAbsPath(attachment, ...) を解決し、deps.resolveLink({ sourcePath, vaultName: \${attachment.key}-${filename}` })` を呼び出します — 攻撃者が選択した任意の絶対パスのコピーをvaultにキューイングします。
apps/obsidian/src/services/attachment-import/service.ts:125-155 — AttachmentImportBatch.resolveLink() は { source: sourcePath, dest: <vault attachment folder>/<key>-<filename> } をキューイングし、attachment.import 設定(デフォルトは true、apps/obsidian/src/services/settings/schema.ts:123)によってのみ制御されます。
apps/obsidian/src/lib/copy-attachments.ts:43-60 — copyAttachment() はパス検証なしで実際の読み取り+書き込みを実行します: stat(source) の後に copyFile(source, dest)。
正味の効果: 攻撃者が linked_file (linkMode 2) のZoteroアイテムを被害者のライブラリに入れることができれば — 例えば共有Zoteroグループライブラリ、被害者がインポートする .rdf/.json/Better BibTeXエクスポート、または攻撃者が書き込みアクセス権を持つ同期ライブラリ — そのアイテムの添付パスを被害者のディスク上の任意のファイル(~/.ssh/id_rsa、ブラウザの資格情報ストア、他のvault、.env ファイルなど)に設定できます。被害者がデフォルトの attachment.import: true 設定でZotLitのノートインポートを実行した瞬間(またはその注釈をレンダリング/埋め込みした瞬間)、ZotLitはそのファイルの内容を予測可能な名前(<attachmentKey>-<basename>)で被害者のObsidian vaultに黙ってコピーします。vaultは日常的に同期、gitコミット、または公開されるため、これは攻撃者が他の方法では決して到達できなかったデータを、攻撃者(または同期ターゲットにアクセスできる誰か)が読み取れる場所に移動させます。
関連する未検証の観察(この脆弱性のPoCの一部ではありません): attachmentFilename() の兄弟分岐である "storage" ブランチ(zt-template-attach.ts:103-104)は、他の2つのブランチに適用されている basename() 呼び出しなしで生のパスサフィックスを返します。これが独立して悪用可能かどうかは、Zotero自身のストレージ同期メカニズムがローカルにダウンロードされたファイルにどのように名前を付けるかに依存し、これはこのリポジトリの範囲外であり検証されていません — 一貫性のために閉じる価値のあるハードニングのギャップとしてここにフラグを立てるのみです。
概念実証
動的検証: 正確な脆弱な関数(parseAttachmentPath、attachmentAbsPath、attachmentFilename、copyAttachments/copyAttachment/writeCopy/destMatches、isErrno、reflink)を、出荷されたソースから逐語的に(上記でファイル:行を引用、ロジックは変更なし)抽出し、Nodeで直接実行しました。これは、このサンドボックスでは完全なオフライン pnpm ワークスペースインストールが利用できなかったためです(corepack/pnpm ツールチェーンがオフラインで壊れている)。唯一の置換は LogTape ロガー呼び出しを console.warn に置き換えたことです — 見た目のみで、制御フローへの影響はゼロです。スクリプト: ~/engagements/zotlit/evidence/poc-verify.mjs。
手順:
~/engagements/zotlit/evidence/victim-disk/id_rsa にシミュレートされた被害者の秘密(ダミー内容、シミュレートと明確に表示)を配置。{ key: "EVILKEY1", path: "<path to victim's id_rsa>", linkMode: 2 } — まさに getAttachmentByKey がZoteroのDBから返す形。attachmentAbsPath() を実行 — ソースを被害者のファイルとして逐語的に解決、検証なし。attachmentFilename() を実行 — id_rsa を安全に見える宛先ベース名として解決。note-parser.ts:427 からの実際の vaultName 構築(${key}-${filename})を再現。copyAttachments() を実行 — 結果 { copied: 1, skipped: 0, missing: 0 }。fake-vault/attachments/EVILKEY1-id_rsa を読み戻し — 内容は被害者の元のファイルとバイト単位で一致。実際のxtermでの完全な実行(コマンド+出力)のスクリーンショット: ../evidence/poc-run.png
影響
被害者のZoteroライブラリの内容に影響を与えることができる攻撃者(共有/グループライブラリ、インポートされた文献エクスポート、または同期ライブラリ)は、被害者のOSユーザーが読み取れる任意のローカルファイル — SSHキー、資格情報ストア、他のvaultの内容、.env/設定の秘密 — を、被害者がデフォルト設定でZotLitの中核的なノートインポート機能を使用した瞬間に、プロンプトや確認なしで被害者のObsidian vaultに窃取できます。vaultは一般的に同期(Obsidian Sync、git、クラウドフォルダ)または公開されるため、これはローカルファイル読み取りプリミティブを現実的なリモート露出に変換します。
脆弱性
修復
linked_file (linkMode 2) 添付ソースを、被害者が明示的に設定または承認したファイルシステムの場所(例えばZotero自身が設定した baseAttachmentPath、またはZoteroデータディレクトリ)に制限し、パスが期待されるルート外にあるリンクされた添付ファイルを自動コピーする前にユーザーにプロンプトを表示することを提案します。多層防御として、attachmentFilename() の linked-absolute/linked-base ブランチですでに使用されている basename() のみのサニタイズを "storage" ブランチにも適用し、どのコードパスもサニタイズされていないファイル名フラグメントを返さないようにすることを検討してください。
クレジット
Dostxodjayev Abdullox (GitHub: squeeze440)
報告チャネル
aidenlx/zotlit ではプライベート脆弱性報告が有効になっています — 公開イシューではなく、リポジトリのGitHub Security Advisory (GHSA) ドラフトを介して報告してください。