
PoC — path traversal tramite sincronizzazione malevola del dispositivo nel plugin Supernote Obsidian (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6).
Stato CVE: richiesto, in attesa di assegnazione. Questa segnalazione è pubblicata come GHSA-3gx3-r874-5pp4. All'assegnazione del CVE questo repository viene rinominato
CVE-YYYY-NNNNN-supernote-obsidian-plugin-PoCe questo banner viene sostituito con il link al CVE.
| Ricercatore | Dostxodjayev Abdullox (@squeeze440) |
| Advisory | GHSA-3gx3-r874-5pp4 |
| CVSS 3.1 | 5.6 (Medio) |
| Debolezza | CWE-22, CWE-73 |
Il Path Traversal nella funzionalità di sincronizzazione automatica del dispositivo del plugin Obsidian Supernote (Unofficial) (philips/supernote-obsidian-plugin) v2.9.1 consente a un dispositivo "Supernote" malevolo o compromesso (o a un attaccante on-path/on-LAN che si spaccia per l'IP del dispositivo associato) di far scrivere al client Obsidian della vittima un file nuovo arbitrario ovunque il processo desktop possa scrivere — anche al di fuori della cartella di sincronizzazione configurata e al di fuori del vault stesso — tramite un campo uri appositamente craftato nella risposta di elenco directory del dispositivo.
philips/supernote-obsidian-plugin ("Supernote (Unofficial)"), un plugin della community di Obsidian.md che sincronizza le note di un dispositivo fisico e-ink Supernote in un vault tramite il server HTTP locale "Browse and Access" del dispositivo (http://<device-ip>:8089, senza autenticazione, per design della funzionalità del dispositivo).
48db5bf4dcfb100632c84699c830c1309da1abb9supernote-typescript: commit 195415b3a1f74147... (non direttamente coinvolto — il bug è interamente nel codice di pianificazione della sincronizzazione del plugin stesso)5.6 Medio — CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
AV:A — il server HTTP del dispositivo è in chiaro, non autenticato e configurato tramite un semplice indirizzo IPv4 (IP_VALIDATION_PATTERN, src/settings.ts:6); raggiungerlo come "il dispositivo" richiede una posizione di rete adiacente alla LAN (rogue AP, ARP spoofing o rivendicazione dell'IP), non un accesso remoto arbitrario.AC:H — lo sfruttamento dipende dal fatto che l'attaccante occupi già quella posizione di rete quando il plugin della vittima comunica con directConnectIP:8089; non è un trigger remoto one-shot.UI:R — richiede che la vittima esegua "Sync supernote notes now" (o abbia già abilitato l'interruttore di sincronizzazione automatica) mentre è puntata all'endpoint controllato dall'attaccante.S:C — il componente vulnerabile è un plugin Obsidian con scope sul vault; la scrittura finisce completamente al di fuori del vault, sul filesystem host sottostante, che è uno scope di sicurezza diverso.C:N — questa è una primitiva di sola scrittura; nulla viene letto dall'attaccante.I:H — byte controllati dall'attaccante finiscono in un percorso scelto dall'attaccante con un nome file scelto dall'attaccante. Avvertenza limitante: writeBinaryAt() (src/syncEngine.ts:40-47) prende il ramo create solo per percorsi non già tracciati nel manifest di sincronizzazione del plugin stesso, e Vault.createBinary() di Obsidian rifiuta di sovrascrivere silenziosamente un file che esiste già fisicamente nel percorso risolto (confermato empiricamente — vedi PoC) — quindi si tratta di "piantare un nuovo file ovunque", non di "sovrascrivere qualsiasi file esistente".A:N — nessun impatto sulla disponibilità dimostrato.runDeviceSync() (src/syncEngine.ts:100-196) elenca i file del dispositivo associato tramite scanDeviceSupernoteTree() (src/FileListModal.ts:53-68), che ricorre l'elenco directory HTTP del dispositivo stesso e mantiene qualsiasi voce il cui name corrisponda a /\.(note|spd)$/i (src/FileListModal.ts:46,63). Il campo uri di ciascuna voce — una stringa separata e controllata indipendentemente nello stesso oggetto JSON restituito dal dispositivo — non viene mai validato rispetto a name o a qualsiasi altra cosa.
Quel uri grezzo viene poi passato direttamente a deviceUriToVaultPath() (src/deviceSync.ts:153-161):
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 rimuove \ : * ? " < > | ma non rimuove né rifiuta mai i segmenti di percorso ... Un uri del dispositivo pari a /../../PWNED.txt sopravvive intatto e viene unito alla cartella di sincronizzazione configurata (predefinita "Supernote sync") per produrre vaultPath = "Supernote sync/../../PWNED.txt".
syncEngine.ts:128 calcola questo vaultPath dall'uri grezzo dell'elenco, poi ensureFolder()/writeBinaryAt() (syncEngine.ts:146-148, 21-47) lo passano direttamente a app.vault.getAbstractFileByPath() / createBinary() / modifyBinary() senza alcun controllo di traversal. La risoluzione dei percorsi di Obsidian normalizza poi i segmenti .. rispetto alla directory reale del vault su disco, facendo atterrare la scrittura sopra la cartella di sincronizzazione — e, con abbastanza segmenti ../, sopra la radice del vault, sul filesystem host, nello scope di permessi dell'utente OS stesso.
Controllo sui sibling: ogni altro sink di scrittura sul vault in questo codebase (src/main.ts — cattura screen-mirror, importazione PDF/markdown, "attach to note", DownloadListModal in src/FileListModal.ts:245-246) costruisce il proprio percorso di destinazione tramite app.fileManager.getAvailablePathForAttachment(file.name) di Obsidian, che consuma sempre e solo il campo name del dispositivo e non è sfruttabile in questo modo. deviceUriToVaultPath() nella funzionalità di sincronizzazione automatica più recente è l'unico sink che invece costruisce a mano un percorso dal campo uri del dispositivo, ed è quello che ha saltato la sanificazione di .. — un caso limpido di "controllato su ogni sibling tranne questo".
L'interfaccia delle impostazioni del plugin stesso afferma: "The sync command never writes anywhere outside this folder" (src/settings.ts, descrizione della cartella di sincronizzazione) — questo PoC falsifica direttamente tale garanzia.
Confermato dinamicamente end-to-end contro un binario desktop Obsidian 1.13.4 reale e non modificato (Xvfb + fluxbox + xdotool + scrot), eseguendo il main.js effettivamente compilato del plugin — nessun mocking del codice del plugin.