
PoC — Path Traversal über bösartige Gerätesynchronisierung im Supernote Obsidian-Plugin (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6).
CVE-Status: beantragt, Zuweisung ausstehend. Dieser Fund wird als GHSA-3gx3-r874-5pp4 veröffentlicht. Bei der CVE-Zuweisung wird dieses Repository in
CVE-YYYY-NNNNN-supernote-obsidian-plugin-PoCumbenannt und dieser Banner durch den CVE-Link ersetzt.
| Forscher | Dostxodjayev Abdullox (@squeeze440) |
| Advisory | GHSA-3gx3-r874-5pp4 |
| CVSS 3.1 | 5.6 (Mittel) |
| Schwäche | CWE-22, CWE-73 |
Path Traversal in der Auto-Sync-Funktion des Supernote (Unofficial) Obsidian-Plugins (philips/supernote-obsidian-plugin) v2.9.1 ermöglicht es einem bösartigen oder kompromittierten „Supernote"-Gerät (oder einem On-Path-/On-LAN-Angreifer, der die IP des gekoppelten Geräts imitiert), den Obsidian-Client des Opfers dazu zu bringen, eine beliebige neue Datei überall dort zu schreiben, wo der Desktop-Prozess Schreibzugriff hat — einschließlich außerhalb des konfigurierten Sync-Ordners und außerhalb des Vaults selbst — über ein manipuliertes uri-Feld in der Verzeichnisauflistungsantwort des Geräts.
philips/supernote-obsidian-plugin („Supernote (Unofficial)"), ein Obsidian.md-Community-Plugin, das die Notizen eines physischen Supernote-E-Ink-Geräts über den lokalen „Browse and Access"-HTTP-Server des Geräts (http://<device-ip>:8089, ohne Authentifizierung, bedingt durch das Design der Gerätefunktion) in einen Vault synchronisiert.
48db5bf4dcfb100632c84699c830c1309da1abb9supernote-typescript: Commit 195415b3a1f74147... (selbst nicht betroffen — der Fehler liegt vollständig im eigenen Sync-Planungscode des Plugins)5.6 Mittel — CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
AV:A — der HTTP-Server des Geräts ist unverschlüsselt, unauthentifiziert und wird durch eine bloße IPv4-Adresse konfiguriert (IP_VALIDATION_PATTERN, src/settings.ts:6); um ihn als „das Gerät" zu erreichen, ist eine LAN-nahe Netzwerkposition erforderlich (Rogue AP, ARP-Spoofing oder Beanspruchung der IP), kein beliebiger Fernzugriff.AC:H — die Ausnutzung hängt davon ab, dass der Angreifer diese Netzwerkposition bereits einnimmt, wenn das Plugin des Opfers mit directConnectIP:8089 kommuniziert; es ist kein einmaliger Fernauslöser.UI:R — erfordert, dass das Opfer „Sync supernote notes now" ausführt (oder dass der Auto-Sync-Schalter bereits aktiviert ist), während es auf den angreiferkontrollierten Endpunkt zeigt.S:C — die verwundbare Komponente ist ein Vault-gebundenes Obsidian-Plugin; der Schreibvorgang landet vollständig außerhalb des Vaults, auf dem zugrunde liegenden Host-Dateisystem, was einen anderen Sicherheitsbereich darstellt.C:N — dies ist ein reines Schreib-Primitiv; nichts wird an den Angreifer zurückgelesen.I:H — angreiferkontrollierte Bytes landen an einem vom Angreifer gewählten Pfad mit einem vom Angreifer gewählten Dateinamen. Eingeschränkter Vorbehalt: writeBinaryAt() (src/syncEngine.ts:40-47) nimmt den Erstellungs-Zweig nur für Pfade, die nicht bereits im eigenen Sync-Manifest des Plugins erfasst sind, und Obsidians Vault.createBinary() weigert sich selbst, eine Datei stillschweigend zu überschreiben, die bereits physisch am aufgelösten Pfad existiert (empirisch bestätigt — siehe PoC) — es handelt sich also um „eine neue Datei überall platzieren", nicht um „jede vorhandene Datei überschreiben".A:N — keine Auswirkung auf die Verfügbarkeit nachgewiesen.runDeviceSync() (src/syncEngine.ts:100-196) listet die Dateien des gekoppelten Geräts über scanDeviceSupernoteTree() (src/FileListModal.ts:53-68) auf, das die eigene HTTP-Verzeichnisauflistung des Geräts rekursiv durchläuft und jeden Eintrag behält, dessen name mit /\.(note|spd)$/i übereinstimmt (src/FileListModal.ts:46,63). Das uri-Feld jedes Eintrags — ein separates, unabhängig kontrolliertes String-Feld im selben vom Gerät zurückgegebenen JSON-Objekt — wird niemals gegen name oder irgendetwas anderes validiert.
Diese rohe uri wird dann direkt in deviceUriToVaultPath() (src/deviceSync.ts:153-161) eingespeist:
const INVALID_FILENAME_CHARS = /[\\:*?"<>|]/g; // deviceSync.ts:144
export function deviceUriToVaultPath(syncFolder: string, deviceUri: string): string {
const segments = deviceUri
.split('/')
.filter((s) => s.length > 0)
.map((s) => s.replace(INVALID_FILENAME_CHARS, '_'));
const cleanRoot = syncFolder.replace(/^\/+|\/+$/g, '');
return cleanRoot ? `${cleanRoot}/${segments.join('/')}` : segments.join('/');
}
INVALID_FILENAME_CHARS entfernt \ : * ? " < > |, entfernt oder verwirft jedoch niemals ..-Pfadsegmente. Eine Geräte-uri von /../../PWNED.txt überlebt unverändert und wird an den konfigurierten Sync-Ordner (Standard "Supernote sync") angehängt, um vaultPath = "Supernote sync/../../PWNED.txt" zu erzeugen.
syncEngine.ts:128 berechnet diesen vaultPath aus der rohen uri der Auflistung, dann übergeben ensureFolder()/writeBinaryAt() (syncEngine.ts:146-148, 21-47) ihn direkt an app.vault.getAbstractFileByPath() / createBinary() / modifyBinary() ohne Traversal-Prüfung. Obsidians eigene Pfadauflösung normalisiert dann die ..-Segmente gegen das reale Vault-Verzeichnis auf der Festplatte, wodurch der Schreibvorgang oberhalb des Sync-Ordners landet — und mit genügend ../-Segmenten oberhalb des Vault-Stammverzeichnisses, auf dem Host-Dateisystem, im Berechtigungsbereich des OS-Benutzers selbst.
Geschwisterprüfung: Jede andere Vault-Schreib-Senke in dieser Codebasis (src/main.ts — Screen-Mirror-Aufnahme, PDF/Markdown-Import, „attach to note", DownloadListModal in src/FileListModal.ts:245-246) erstellt ihren Zielpfad über Obsidians eigenes app.fileManager.getAvailablePathForAttachment(file.name), das ausschließlich das name-Feld des Geräts verwendet und auf diese Weise nicht ausnutzbar ist. deviceUriToVaultPath() in der neueren Auto-Sync-Funktion ist die einzige Senke, die stattdessen einen Pfad aus dem uri-Feld des Geräts von Hand zusammenbaut, und sie ist diejenige, die die ..-Bereinigung übersprungen hat — ein klarer Fall von „bei jedem Geschwister geprüft, nur bei diesem nicht".
Die eigene Einstellungs-UI des Plugins besagt: „The sync command never writes anywhere outside this folder" (src/settings.ts, Sync folder description) — dieser PoC widerlegt diese Garantie direkt.
Dynamisch end-to-end bestätigt gegen ein echtes, unverändertes Obsidian 1.13.4 Desktop-Binary (Xvfb + fluxbox + xdotool + scrot), das die tatsächlich kompilierte main.js des Plugins ausführt — kein Mocking des Plugin-Codes.