Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
supernote-obsidian-plugin-PoC — PoC — path traversal via une synchronisation d'appareil malveillante dans le plugin Supernote Obsidian (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6). | Kitploit
Outils/GitHubGitHub/squeeze440/supernote-obsidian-plugin-poc
Analyse des VulnérabilitésExploitationExploitation d'Applications WebVirtualisation de SécuritéTests d'IntrusionArticles et Recherche
GitHubsqueeze440/supernote-obsidian-plugin-poc

supernote-obsidian-plugin-PoC

PoC — path traversal via une synchronisation d'appareil malveillante dans le plugin Supernote Obsidian (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6).

Voir le dépôt
il y a 7 joursPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Résumé

Statut CVE : demandé, en attente d'attribution. Cette découverte est publiée sous GHSA-3gx3-r874-5pp4. À l'attribution du CVE, ce dépôt sera renommé CVE-YYYY-NNNNN-supernote-obsidian-plugin-PoC et cette bannière sera remplacée par le lien CVE.

ChercheurDostxodjayev Abdullox (@squeeze440)
AvisGHSA-3gx3-r874-5pp4
CVSS 3.15.6 (Moyen)
FaiblesseCWE-22, CWE-73

Résumé

Une traversée de chemin (Path Traversal) dans la fonctionnalité de synchronisation automatique de l'appareil du plugin Obsidian Supernote (Unofficial) (philips/supernote-obsidian-plugin) v2.9.1 permet à un appareil « Supernote » malveillant ou compromis (ou à un attaquant sur le chemin réseau/sur le LAN usurpant l'IP de l'appareil appairé) de faire écrire par le client Obsidian de la victime un nouveau fichier arbitraire partout où le processus de bureau peut écrire — y compris en dehors du dossier de synchronisation configuré et en dehors du coffre lui-même — via un champ uri forgé dans la réponse de listage de répertoire de l'appareil.

Produit

philips/supernote-obsidian-plugin (« Supernote (Unofficial) »), un plugin communautaire Obsidian.md qui synchronise les notes d'un appareil physique Supernote à encre électronique dans un coffre via le serveur HTTP local « Browse and Access » de l'appareil (http://<device-ip>:8089, sans authentification, par conception de la fonctionnalité de l'appareil).

Version testée

  • Plugin : v2.9.1, commit 48db5bf4dcfb100632c84699c830c1309da1abb9
  • Sous-module supernote-typescript : commit 195415b3a1f74147... (non impliqué en lui-même — le bug se trouve entièrement dans le code de planification de synchronisation propre au plugin)
  • Application hôte : Obsidian desktop 1.13.4 (binaire réel, testé dynamiquement)

CVSS v3.1 estimé

5.6 Moyen — CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N

  • AV:A — le serveur HTTP de l'appareil est en clair, non authentifié, et configuré par une simple adresse IPv4 (IP_VALIDATION_PATTERN, src/settings.ts:6) ; y accéder en tant que « l'appareil » nécessite une position réseau adjacente au LAN (AP pirate, usurpation ARP, ou revendication de l'IP), et non un accès distant arbitraire.
  • AC:H — l'exploitation dépend du fait que l'attaquant occupe déjà cette position réseau lorsque le plugin de la victime communique avec directConnectIP:8089 ; il ne s'agit pas d'un déclenchement distant en un seul coup.
  • UI:R — nécessite que la victime exécute « Sync supernote notes now » (ou que le basculement de synchronisation automatique soit déjà activé) alors qu'elle pointe vers le point de terminaison contrôlé par l'attaquant.
  • S:C — le composant vulnérable est un plugin Obsidian limité au coffre ; l'écriture atterrit entièrement en dehors du coffre, sur le système de fichiers hôte sous-jacent, ce qui constitue une portée de sécurité différente.
  • C:N — il s'agit d'une primitive en écriture seule ; rien n'est relu par l'attaquant.
  • I:H — des octets contrôlés par l'attaquant atterrissent à un chemin choisi par l'attaquant avec un nom de fichier choisi par l'attaquant. Réserve limitative : writeBinaryAt() (src/syncEngine.ts:40-47) ne prend la branche create que pour les chemins non déjà suivis dans le manifeste de synchronisation propre au plugin, et Vault.createBinary() d'Obsidian refuse lui-même d'écraser silencieusement un fichier qui existe déjà physiquement au chemin résolu (confirmé empiriquement — voir PoC) — il s'agit donc de « planter un nouveau fichier n'importe où », et non d'« écraser n'importe quel fichier existant ».
  • A:N — aucun impact sur la disponibilité démontré.

Détails

runDeviceSync() (src/syncEngine.ts:100-196) liste les fichiers de l'appareil appairé via scanDeviceSupernoteTree() (src/FileListModal.ts:53-68), qui parcourt récursivement le listage de répertoire HTTP de l'appareil lui-même et conserve toute entrée dont le name correspond à /\.(note|spd)$/i (src/FileListModal.ts:46,63). Le champ uri de chaque entrée — une chaîne distincte et contrôlée indépendamment dans le même objet JSON renvoyé par l'appareil — n'est jamais validé par rapport à name ni par rapport à quoi que ce soit d'autre.

Cet uri brut est ensuite injecté directement dans deviceUriToVaultPath() (src/deviceSync.ts:153-161) :

root@kitploit:~
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 supprime \ : * ? " < > | mais ne supprime ni ne rejette jamais les segments de chemin ... Un uri d'appareil /../../PWNED.txt survit intact et est joint au dossier de synchronisation configuré (par défaut "Supernote sync") pour produire vaultPath = "Supernote sync/../../PWNED.txt".

syncEngine.ts:128 calcule ce vaultPath à partir de l'uri brut du listage, puis ensureFolder()/writeBinaryAt() (syncEngine.ts:146-148, 21-47) le transmettent directement à app.vault.getAbstractFileByPath() / createBinary() / modifyBinary() sans aucune vérification de traversée. La résolution de chemin propre à Obsidian normalise alors les segments .. par rapport au répertoire réel du coffre sur le disque, faisant atterrir l'écriture au-dessus du dossier de synchronisation — et, avec suffisamment de segments ../, au-dessus de la racine du coffre entièrement, sur le système de fichiers hôte, dans la portée de permissions de l'utilisateur du système d'exploitation.

Vérification des pairs : tous les autres points d'écriture dans le coffre de cette base de code (src/main.ts — capture de miroir d'écran, import PDF/markdown, « attach to note », DownloadListModal dans src/FileListModal.ts:245-246) construisent leur chemin de destination via app.fileManager.getAvailablePathForAttachment(file.name) d'Obsidian lui-même, qui ne consomme jamais que le champ name de l'appareil et n'est pas exploitable de cette manière. deviceUriToVaultPath() dans la fonctionnalité de synchronisation automatique plus récente est le seul point d'écriture qui fabrique à la main un chemin à partir du champ uri de l'appareil, et c'est celui qui a omis la désinfection des .. — un cas typique de « vérifié sur tous les pairs sauf celui-ci ».

L'interface de paramètres du plugin lui-même déclare : « La commande de synchronisation n'écrit jamais en dehors de ce dossier » (src/settings.ts, description du dossier de synchronisation) — ce PoC falsifie directement cette garantie.

Preuve de concept

Confirmé dynamiquement de bout en bout contre un binaire Obsidian 1.13.4 desktop réel et non modifié (Xvfb + fluxbox + xdotool + scrot), exécutant le main.js compilé réel du plugin — aucun mockage du code du plugin.

  1. Compilation du plugin depuis les sources (./scripts/build) et chargement dans un coffre de test vierge (Supernote (Unofficial) v2.9.1, activé via « Trust author and enable plugins »).
  2. Réglage du paramètre du plugin Supernote IP address sur 127.0.0.1, Sync folder laissé à sa valeur par défaut Supernote sync.
  3. Mise en place d'un mock Node à un seul fichier du serveur « Browse and Access » de l'appareil sur 127.0.0.1:8089, simulant un appareil malveillant/compromis. Son listage de répertoire renvoie :
    root@kitploit:~
    {"name":"Quick notes.note","size":61,"date":"2026-07-31 00:00:00",
     "uri":"/../../PWNED_BY_DEVICE_SYNC.txt","extension":"note","isDirectory":false}
    
    (name passe le filtre d'extension .note ; uri porte la traversée.) Toute requête GET reçoit une réponse 200 avec les octets du fichier — les clients HTTP réels normalisent les .. hors du chemin de requête sortant avant qu'il n'atteigne le réseau (RFC 3986), ce qui est sans importance ici puisque le code vulnérable calcule le chemin d'écriture à partir de la chaîne JSON d'origine, et non de l'URL de requête normalisée.
  4. Exécution de l'action de la palette de commandes « Supernote (Unofficial): Sync supernote notes now ».
  5. Résultat : un nouveau fichier, PWNED_BY_DEVICE_SYNC.txt, contenant les octets de l'attaquant, a été créé un répertoire au-dessus de la racine du coffre (frère de testvault/, entièrement en dehors du coffre). Le data.json du plugin a enregistré le chemin calculé mot pour mot : "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt".

Preuves (captures authentiques, ~/engagements/supernote-obsidian-plugin/evidence/) :

  • 01-vault-escape-file-write.png — terminal réel : ls -la testvault/ PWNED_BY_DEVICE_SYNC.txt montrant le fichier comme frère du répertoire du coffre, plus son contenu contrôlé par l'attaquant via cat.
  • 02-plugin-data-json-vaultpath.png — le fichier d'état de synchronisation persisté par le plugin lui-même enregistrant "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt".
  • 03-plugin-settings-security-claim.png — l'interface de paramètres du plugin affirmant que la commande de synchronisation « n'écrit jamais en dehors de ce dossier ».

Impact

Un attaquant capable de répondre en tant qu'appareil Supernote configuré de la victime (adjacent au LAN : AP pirate, usurpation ARP, ou revendication de l'IP de l'appareil) peut faire créer par le client Obsidian de la victime un nouveau fichier contrôlé par l'attaquant à n'importe quel chemin du système de fichiers où le processus de bureau peut écrire — à l'intérieur du coffre (par exemple un tout nouveau fichier sous .obsidian/plugins/<new-id>/, préparant le terrain pour d'autres abus de confiance de plugins) ou entièrement en dehors (par exemple ~/.config/autostart/*.desktop, un nouveau fichier déposé dans crontab, ou tout autre emplacement où un nouveau fichier — et non un écrasement — suffit à obtenir une exécution ou une persistance). Il ne peut pas écraser silencieusement un fichier qui existe déjà au chemin résolu (createBinary d'Obsidian lève « File already exists » dans ce cas, confirmé empiriquement), ce qui limite la primitive au dépôt de fichiers entièrement nouveaux plutôt qu'à un écrasement universel.

Faiblesses

  • CWE-22 : Limitation incorrecte d'un nom de chemin à un répertoire restreint (« Path Traversal »)
  • CWE-73 : Contrôle externe du nom de fichier ou du chemin (l'uri fourni par l'appareil détermine directement la destination sur le disque)

Remédiation

Dans deviceUriToVaultPath() (src/deviceSync.ts:153-161), rejeter ou supprimer les segments de chemin .. (ainsi que les segments vides/ne contenant que .) après avoir découpé deviceUri, par exemple :

root@kitploit:~
const segments = deviceUri
    .split('/')
    .filter((s) => s.length > 0 && s !== '.' && s !== '..')
    .map((s) => s.replace(INVALID_FILENAME_CHARS, '_'));

De plus, scanDeviceSupernoteTree() (src/FileListModal.ts:63) devrait valider que l'uri d'une entrée de listage est cohérent avec son name (par exemple que l'uri se termine par le même nom de fichier), plutôt que de faire confiance aux deux champs indépendamment — la même classe de défense déjà implicitement utilisée partout ailleurs dans la base de code, qui ne construit des chemins qu'à partir de name/basename via getAvailablePathForAttachment().

Crédit

Dostxodjayev Abdullox

Canal de signalement

Aucun SECURITY.md n'est présent dans ce dépôt. Le signalement privé de vulnérabilités GitHub est confirmé activé pour philips/supernote-obsidian-plugin (gh api repos/philips/supernote-obsidian-plugin/private-vulnerability-reporting --jq .enabled → true ; 0 avis de sécurité publié antérieurement). Le flux GHSA standard s'applique : https://github.com/philips/supernote-obsidian-plugin/security/advisories/new.

Télécharger l’outil