
PoC — Supernote Obsidian प्लगइन में दुर्भावनापूर्ण डिवाइस सिंक के माध्यम से पाथ ट्रैवर्सल (GHSA-3gx3-r874-5pp4, CVE-2026-86999, CVSS 5.6)।
CVE स्थिति: अनुरोध किया गया, असाइनमेंट लंबित। यह निष्कर्ष GHSA-3gx3-r874-5pp4 के रूप में प्रकाशित किया गया है। CVE असाइनमेंट पर इस रिपॉज़िटरी का नाम बदलकर
CVE-YYYY-NNNNN-supernote-obsidian-plugin-PoCकर दिया जाएगा और इस बैनर को CVE लिंक से बदल दिया जाएगा।
| शोधकर्ता | Dostxodjayev Abdullox (@squeeze440) |
| सलाहकार | GHSA-3gx3-r874-5pp4 |
| CVSS 3.1 | 5.6 (मध्यम) |
| कमज़ोरी | CWE-22, CWE-73 |
Supernote (Unofficial) Obsidian प्लगइन (philips/supernote-obsidian-plugin) v2.9.1 की डिवाइस ऑटो-सिंक सुविधा में पाथ ट्रैवर्सल एक दुर्भावनापूर्ण या अनधिकृत "Supernote" डिवाइस (या पेयर किए गए डिवाइस के IP का प्रतिरूपण करने वाले ऑन-पाथ/ऑन-LAN हमलावर) को सक्षम बनाता है कि वह पीड़ित के Obsidian क्लाइंट को डिवाइस की डायरेक्टरी-लिस्टिंग प्रतिक्रिया में तैयार किए गए uri फ़ील्ड के माध्यम से डेस्कटॉप प्रक्रिया जहाँ भी लिख सकती है वहाँ कोई भी नई फ़ाइल लिखवा सके — जिसमें कॉन्फ़िगर किए गए सिंक फ़ोल्डर के बाहर और स्वयं वॉल्ट के बाहर भी शामिल है।
philips/supernote-obsidian-plugin ("Supernote (Unofficial)"), एक Obsidian.md कम्युनिटी प्लगइन जो एक भौतिक Supernote ई-इंक डिवाइस के नोट्स को डिवाइस के स्थानीय "Browse and Access" HTTP सर्वर (http://<device-ip>:8089, डिवाइस सुविधा के डिज़ाइन के अनुसार, बिना प्रमाणीकरण) के माध्यम से एक वॉल्ट में सिंक करता है।
48db5bf4dcfb100632c84699c830c1309da1abb9supernote-typescript: कमिट 195415b3a1f74147... (स्वयं इसमें शामिल नहीं — बग पूरी तरह से प्लगइन के अपने सिंक-प्लानिंग कोड में है)5.6 मध्यम — CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:N/I:H/A:N
AV:A — डिवाइस का HTTP सर्वर प्लेनटेक्स्ट, बिना प्रमाणीकरण वाला है, और एक नंगे IPv4 पते (IP_VALIDATION_PATTERN, src/settings.ts:6) द्वारा कॉन्फ़िगर किया गया है; इसे "डिवाइस" के रूप में पहुँचने के लिए LAN-निकट नेटवर्क स्थिति (रोग AP, ARP स्पूफिंग, या IP का दावा) की आवश्यकता होती है, मनमानी दूरस्थ पहुँच की नहीं।AC:H — शोषण इस बात पर निर्भर करता है कि हमलावर पहले से ही उस नेटवर्क स्थिति में हो जब पीड़ित का प्लगइन directConnectIP:8089 से बात करता है; यह एक-शॉट दूरस्थ ट्रिगर नहीं है।UI:R — इसके लिए पीड़ित को "Sync supernote notes now" चलाने की आवश्यकता होती है (या ऑटो-सिंक टॉगल पहले से सक्षम हो) जबकि हमलावर-नियंत्रित एंडपॉइंट की ओर इंगित हो।S:C — कमज़ोर घटक एक वॉल्ट-स्कोप्ड Obsidian प्लगइन है; लेखन पूरी तरह से वॉल्ट के बाहर, अंतर्निहित होस्ट फ़ाइलसिस्टम पर होता है, जो एक अलग सुरक्षा स्कोप है।C:N — यह एक राइट-ओनली प्रिमिटिव है; हमलावर को कुछ भी वापस पढ़ा नहीं जाता।I:H — हमलावर-नियंत्रित बाइट्स हमलावर-चुने गए पथ पर हमलावर-चुने गए फ़ाइलनाम के साथ पहुँचते हैं। सीमित चेतावनी: writeBinaryAt() (src/syncEngine.ts:40-47) केवल उन पथों के लिए create शाखा लेता है जो प्लगइन के अपने सिंक मैनिफ़ेस्ट में पहले से ट्रैक नहीं हैं, और Obsidian का Vault.createBinary() स्वयं उस फ़ाइल को चुपचाप ओवरराइट करने से इनकार करता है जो पहले से ही हल किए गए पथ पर भौतिक रूप से मौजूद है (अनुभवजन्य रूप से पुष्टि की गई — PoC देखें) — इसलिए यह "कहीं भी एक नई फ़ाइल रखना" है, "किसी भी मौजूदा फ़ाइल को ओवरराइट करना" नहीं।A:N — कोई उपलब्धता प्रभाव प्रदर्शित नहीं किया गया।runDeviceSync() (src/syncEngine.ts:100-196) पेयर किए गए डिवाइस की फ़ाइलों को scanDeviceSupernoteTree() (src/FileListModal.ts:53-68) के माध्यम से सूचीबद्ध करता है, जो डिवाइस की अपनी HTTP डायरेक्टरी लिस्टिंग को पुनरावर्ती रूप से पढ़ता है और किसी भी ऐसी प्रविष्टि को रखता है जिसका name /\.(note|spd)$/i (src/FileListModal.ts:46,63) से मेल खाता है। प्रत्येक प्रविष्टि का uri फ़ील्ड — डिवाइस द्वारा लौटाए गए उसी JSON ऑब्जेक्ट में एक अलग, स्वतंत्र रूप से नियंत्रित स्ट्रिंग — को कभी भी name या किसी और चीज़ के विरुद्ध मान्य नहीं किया जाता।
वह कच्चा uri फिर सीधे 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 \ : * ? " < > | को हटाता है लेकिन .. पथ खंडों को कभी हटाता या अस्वीकार नहीं करता। /../../PWNED.txt का डिवाइस uri अछूता बच जाता है और कॉन्फ़िगर किए गए सिंक फ़ोल्डर (डिफ़ॉल्ट "Supernote sync") पर जोड़ दिया जाता है ताकि vaultPath = "Supernote sync/../../PWNED.txt" उत्पन्न हो।
syncEngine.ts:128 इस vaultPath को लिस्टिंग के कच्चे uri से गणना करता है, फिर ensureFolder()/writeBinaryAt() (syncEngine.ts:146-148, 21-47) इसे सीधे app.vault.getAbstractFileByPath() / createBinary() / modifyBinary() को पास करते हैं बिना किसी ट्रैवर्सल जाँच के। Obsidian का अपना पथ रिज़ॉल्यूशन फिर डिस्क पर वास्तविक वॉल्ट डायरेक्टरी के विरुद्ध .. खंडों को सामान्यीकृत करता है, जिससे लेखन सिंक फ़ोल्डर के ऊपर — और, पर्याप्त ../ खंडों के साथ, पूरी तरह से वॉल्ट रूट के ऊपर, होस्ट फ़ाइलसिस्टम पर, OS उपयोगकर्ता की अपनी अनुमति स्कोप में — पहुँच जाता है।
सहोदर जाँच: इस कोडबेस में हर अन्य वॉल्ट-राइट सिंक (src/main.ts — स्क्रीन-मिरर कैप्चर, PDF/markdown आयात, "attach to note", src/FileListModal.ts:245-246 में DownloadListModal) अपने गंतव्य पथ को Obsidian के स्वयं के app.fileManager.getAvailablePathForAttachment(file.name) के माध्यम से बनाता है, जो केवल डिवाइस के name फ़ील्ड का उपभोग करता है और इस तरह शोषणीय नहीं है। नई ऑटो-सिंक सुविधा में deviceUriToVaultPath() एकमात्र सिंक है जो इसके बजाय डिवाइस के uri फ़ील्ड से मैन्युअल रूप से पथ बनाता है, और यही वह है जिसने .. सैनिटाइज़ेशन छोड़ दिया — "हर सहोदर पर जाँच की गई लेकिन इस पर नहीं" का स्पष्ट मामला।
प्लगइन का अपना सेटिंग्स UI कहता है: "The sync command never writes anywhere outside this folder" (src/settings.ts, Sync folder विवरण) — यह PoC उस गारंटी को सीधे गलत साबित करता है।
वास्तविक, असंशोधित Obsidian 1.13.4 डेस्कटॉप बाइनरी (Xvfb + fluxbox + xdotool + scrot) के विरुद्ध एंड-टू-एंड गतिशील रूप से पुष्टि की गई, प्लगइन के वास्तविक संकलित main.js को चलाते हुए — प्लगइन कोड की कोई मॉकिंग नहीं।
./scripts/build) और इसे एक नए परीक्षण वॉल्ट (Supernote (Unofficial) v2.9.1, "Trust author and enable plugins" के माध्यम से सक्षम) में लोड किया।Supernote IP address को 127.0.0.1 पर सेट किया, Sync folder को उसके डिफ़ॉल्ट Supernote sync पर छोड़ दिया।127.0.0.1:8089 पर डिवाइस के "Browse and Access" सर्वर का एक-फ़ाइल Node मॉक खड़ा किया, जो एक दुर्भावनापूर्ण/रोग डिवाइस का अनुकरण करता है। इसकी डायरेक्टरी लिस्टिंग लौटाती है:
{"name":"Quick notes.note","size":61,"date":"2026-07-31 00:00:00",
"uri":"/../../PWNED_BY_DEVICE_SYNC.txt","extension":"note","isDirectory":false}
name .note एक्सटेंशन फ़िल्टर पास करता है; uri ट्रैवर्सल ले जाता है।) किसी भी GET का उत्तर 200 के साथ फ़ाइल बाइट्स से दिया जाता है — वास्तविक HTTP क्लाइंट वायर पर पहुँचने से पहले आउटगोइंग request पथ से .. को सामान्यीकृत कर देते हैं (RFC 3986), जो यहाँ अप्रासंगिक है क्योंकि कमज़ोर कोड write पथ की गणना मूल JSON स्ट्रिंग से करता है, सामान्यीकृत request URL से नहीं।PWNED_BY_DEVICE_SYNC.txt, वॉल्ट रूट से एक डायरेक्टरी ऊपर (testvault/ का सहोदर, पूरी तरह से वॉल्ट के बाहर) बनाई गई। प्लगइन के अपने data.json ने गणना किए गए पथ को शब्दशः दर्ज किया: "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt"।प्रमाण (वास्तविक कैप्चर, ~/engagements/supernote-obsidian-plugin/evidence/):
01-vault-escape-file-write.png — वास्तविक टर्मिनल: ls -la testvault/ PWNED_BY_DEVICE_SYNC.txt जो फ़ाइल को वॉल्ट डायरेक्टरी के सहोदर के रूप में दिखाता है, साथ ही cat के माध्यम से उसकी हमलावर-नियंत्रित सामग्री।02-plugin-data-json-vaultpath.png — प्लगइन की अपनी स्थायी सिंक-स्टेट फ़ाइल जो "vaultPath": "Supernote sync/../../PWNED_BY_DEVICE_SYNC.txt" दर्ज करती है।03-plugin-settings-security-claim.png — प्लगइन का सेटिंग्स UI जो दावा करता है कि सिंक कमांड "never writes anywhere outside this folder."एक हमलावर जो पीड़ित के कॉन्फ़िगर किए गए Supernote डिवाइस के रूप में प्रतिक्रिया दे सकता है (LAN-निकट: रोग AP, ARP स्पूफिंग, या डिवाइस के IP का दावा) पीड़ित के Obsidian क्लाइंट को किसी भी फ़ाइलसिस्टम पथ पर एक नई, हमलावर-नियंत्रित फ़ाइल बनवा सकता है जहाँ डेस्कटॉप प्रक्रिया लिख सकती है — वॉल्ट के अंदर (उदा. .obsidian/plugins/<new-id>/ के अंतर्गत एक बिल्कुल नई फ़ाइल, जो आगे प्लगइन-ट्रस्ट दुरुपयोग की नींव रखती है) या पूरी तरह से इसके बाहर (उदा. ~/.config/autostart/*.desktop, एक नया crontab ड्रॉप-इन, या कोई अन्य स्थान जहाँ एक नई फ़ाइल — ओवरराइट नहीं — निष्पादन या दृढ़ता प्राप्त करने के लिए पर्याप्त है)। यह हल किए गए पथ पर पहले से मौजूद फ़ाइल को चुपचाप ओवरराइट नहीं कर सकता (उस स्थिति में Obsidian का createBinary "File already exists" फेंकता है, अनुभवजन्य रूप से पुष्टि की गई), जो प्रिमिटिव को सार्वभौमिक ओवरराइट के बजाय नेट-न्यू फ़ाइल प्लांटिंग तक सीमित करता है।
uri सीधे डिस्क पर गंतव्य निर्धारित करता है)deviceUriToVaultPath() (src/deviceSync.ts:153-161) में, deviceUri को विभाजित करने के बाद .. (और खाली/केवल-. वाले) पथ खंडों को अस्वीकार या हटाएँ, उदा.:
const segments = deviceUri
.split('/')
.filter((s) => s.length > 0 && s !== '.' && s !== '..')
.map((s) => s.replace(INVALID_FILENAME_CHARS, '_'));
इसके अतिरिक्त, scanDeviceSupernoteTree() (src/FileListModal.ts:63) को यह मान्य करना चाहिए कि लिस्टिंग प्रविष्टि का uri उसके name के अनुरूप है (उदा. uri उसी फ़ाइलनाम पर समाप्त होता है), बजाय दोनों फ़ील्डों पर स्वतंत्र रूप से भरोसा करने के — कोडबेस में हर जगह पहले से ही अंतर्निहित रूप से भरोसा किए जाने वाले उसी प्रकार का बचाव, जो केवल name/basename से getAvailablePathForAttachment() के माध्यम से पथ बनाता है।
Dostxodjayev Abdullox
इस रिपॉज़िटरी में कोई SECURITY.md मौजूद नहीं है। philips/supernote-obsidian-plugin के लिए GitHub निजी कमज़ोरी रिपोर्टिंग सक्षम होने की पुष्टि की गई है (gh api repos/philips/supernote-obsidian-plugin/private-vulnerability-reporting --jq .enabled → true; 0 पूर्व प्रकाशित सुरक्षा सलाहकार)। मानक GHSA प्रवाह लागू होता है: https://github.com/philips/supernote-obsidian-plugin/security/advisories/new।