
Docmost ने एक अटैचमेंट नोड के अंदर javascript: URL को स्वीकार किया, इसे भंडारण और रेंडरिंग के माध्यम से संरक्षित किया, और इसे डॉकमोस्ट ओरिजिन में एक क्लिक करने योग्य एंकर में बदल दिया।
Docmost ने एक अटैचमेंट नोड के अंदर एक javascript: URL को स्वीकार किया, इसे स्टोरेज और रेंडरिंग के दौरान संरक्षित रखा, और इसे Docmost ओरिजिन में एक क्लिक करने योग्य एंकर में बदल दिया।
मैंने Docmost, ओपन-सोर्स सहयोगी दस्तावेज़ीकरण प्लेटफ़ॉर्म में एक उच्च-गंभीरता संग्रहीत XSS समस्या की पहचान की, जिम्मेदारीपूर्वक खुलासा किया और इसे पुन: प्रस्तुत किया।
Docmost की आधिकारिक साइट इसे 3M+ डाउनलोड के साथ एक एंटरप्राइज़-रेडी ऑन-प्रिमाइसेस विकी के रूप में प्रस्तुत करती है, और कहती है कि इस पर Vilnius City, Bechtle, Australian Government, Red Cross, और ETS Quebec सहित संगठनों की टीमों द्वारा भरोसा किया जाता है।
बग एक ऐसी जगह पर बैठा था जो रिच-टेक्स्ट सिस्टम में आसानी से छूट जाती है:
सामान्य लिंक एक्सटेंशन में नहीं, बल्कि फ़ाइल अटैचमेंट के लिए उपयोग किए जाने वाले एक अलग कस्टम नोड प्रकार में।
मैं एक बहुत ही विशिष्ट प्रश्न के साथ एडिटर पाइपलाइन की समीक्षा कर रहा था:
यदि सामान्य लिंक javascript: URL को ब्लॉक करते हैं, तो क्या अटैचमेंट नोड एंकर सिंक तक पहुँचने से पहले उसी नियम को लागू करते हैं?
कमजोर संस्करणों में, उन्होंने ऐसा नहीं किया।
Docmost ने पेज JSON में एक दुर्भावनापूर्ण अटैचमेंट नोड स्वीकार किया, इसके url विशेषता को अपरिवर्तित संग्रहीत किया, और बाद में उस मान को एक क्लिक करने योग्य <a href="javascript:..."> एलिमेंट में वापस रेंडर किया।
वह समस्या CVE-2026-34212 बन गई।
Docmost: docmost/docmost
सलाहकार: GHSA-cf68-cff9-hq4w
CVE: CVE-2026-34212
पैच किया गया संस्करण: v0.71.0
हमलावर-नियंत्रित अटैचमेंट नोड URL -> पेज JSON स्वीकार और अपरिवर्तित संग्रहीत -> HTML/React रेंडरिंग उस URL को एंकर href में बदलता है -> पीड़ित अटैचमेंट क्रिया पर क्लिक करता है -> हमलावर-नियंत्रित JavaScript Docmost ओरिजिन में निष्पादित होता है
Docmost पेज सामग्री को ProseMirror/Tiptap-संगत JSON प्रारूप में संग्रहीत करता है।
उस सामग्री मॉडल में निम्नलिखित के लिए कस्टम ब्लॉक नोड शामिल हैं:
अटैचमेंट नोड निम्नलिखित फ़ील्ड संग्रहीत करता है:
urlnamemimesizeattachmentIdसर्वर पेज सामग्री को कई प्रारूपों में स्वीकार करता है:
jsonmarkdownhtmlऔर इसे संग्रहीत करने से पहले ProseMirror JSON में सामान्यीकृत करता है।
इसका मतलब है कि कोई भी नोड प्रकार जो URL ले जा सकता है, एक सीधी ट्रस्ट सीमा का हिस्सा है।
यदि उन नोड प्रकारों में से कोई अंततः <a href> में रेंडर होता है, तो URL स्कीम हैंडलिंग वैकल्पिक नहीं है।
यह सुरक्षा मॉडल का हिस्सा है।
कस्टम एडिटर एक्सटेंशन सुरक्षा विचलन का एक लगातार स्रोत हैं।
आधार प्रणाली पहले से ही जानती हो सकती है कि खतरनाक URL को सही तरीके से कैसे संभालना है, लेकिन प्रत्येक कस्टम नोड को अभी भी अपने स्वयं के सिंक पर समान नियमों को फिर से लागू करना होता है।
यह एक अनुमानित समीक्षा रणनीति बनाता है:
ठीक यही इस बग को उजागर करता है।
Docmost के सामान्य लिंक एक्सटेंशन ने पहले से ही javascript: को खतरनाक माना।
इसके अटैचमेंट नोड ने ऐसा नहीं किया।
एक बार जब आप यह विषमता देखते हैं, तो सुरक्षा प्रश्न स्पष्ट हो जाता है:
क्या मैं एक अटैचमेंट नोड को बनाए रख सकता हूँ जिसका url javascript: है और इसे एक जीवित एंकर में वापस रेंडर करवा सकता हूँ?
उत्तर हाँ था।
मूल कारण सामग्री नोड प्रकारों में असंगत URL स्वच्छता था।
सर्वर-साइड सामग्री पथ ने मनमाने अटैचमेंट URL को तब तक स्वीकार किया जब तक समग्र सामग्री ProseMirror स्कीमा से मेल खाती थी।
कमजोर संस्करण में:
CreatePageDto ने content?: string | object स्वीकार कियाPageService.parseProsemirrorContent() ने markdown, html, या json को सामान्यीकृत कियाjsonToNode(prosemirrorJson) को कॉल कियाउस सत्यापन चरण ने संरचनात्मक वैधता की जाँच की, URL सुरक्षा की नहीं।
कमजोर सर्वर तर्क का महत्वपूर्ण भाग प्रभावी रूप से था:
prosemirrorJson = content;
jsonToNode(prosemirrorJson);
return prosemirrorJson;
वहाँ कोई अटैचमेंट URL स्कीम सामान्यीकरण नहीं हुआ।
बाद में, अटैचमेंट एक्सटेंशन ने हमलावर-नियंत्रित मान को सीधे रेंडर किया।
कमजोर अटैचमेंट नोड ने ऐसा किया:
url: {
default: "",
parseHTML: (element) => element.getAttribute("data-attachment-url"),
renderHTML: (attributes) => ({
"data-attachment-url": attributes.url,
}),
},
और फिर:
[
"a",
{
href: HTMLAttributes["data-attachment-url"],
class: "attachment",
target: "blank",
},
`${HTMLAttributes["data-attachment-name"]}`,
]
क्लाइंट साइड पर, React नोड व्यू ने इसे फिर से लपेटा:
<a href={getFileUrl(url)} target="_blank">
लेकिन getFileUrl() ने केवल विशेष मामलों को संभाला:
http URL/api/.../files/...बाकी सब कुछ अपरिवर्तित वापस कर दिया गया।
तो एक पेलोड जैसे:
javascript:alert(document.domain)
बच गया:
यह अकेले ही संग्रहीत XSS के लिए पर्याप्त होगा।
मूल कारण को विशेष रूप से स्पष्ट करने वाली बात तुलनात्मक बिंदु है।
Docmost के सामान्य लिंक एक्सटेंशन ने स्पष्ट रूप से javascript: को अवरुद्ध किया:
parseHTML() में javascript: को अस्वीकार कर दियाrenderHTML() में javascript: href को खाली कर दियातो उत्पाद पहले से ही जानता था कि यह स्कीम खतरनाक है।
अटैचमेंट नोड बस उसी नीति को लागू करने में विफल रहा।
यही कारण है कि यह "एडिटर में सामान्य XSS" नहीं था।
यह एक नोड-विशिष्ट ट्रस्ट-बाउंड्री गैप था।
यह बग केवल असुरक्षित HTML सौंदर्यशास्त्र के बारे में नहीं था।
इसने एक हमलावर को जो पेज संपादित कर सकता था, एक दुर्भावनापूर्ण पेलोड को बनाए रखने की अनुमति दी जो बाद में Docmost ओरिजिन में निष्पादित होगा जब कोई अन्य उपयोगकर्ता रेंडर किए गए अटैचमेंट के साथ इंटरैक्ट करेगा।
यह मायने रखता है क्योंकि ओरिजिन-इन स्क्रिप्ट यह कर सकती है:
क्लिक की आवश्यकता इसे एक मामूली समस्या में कम नहीं करती है।
क्लिक सामान्य उत्पाद व्यवहार का हिस्सा है: UI जानबूझकर अटैचमेंट को एक कार्रवाई योग्य लिंक/आइकन के रूप में प्रस्तुत करता है।
तो सुरक्षा प्रश्न यह नहीं है "क्या हमलावर बिना किसी इंटरैक्शन के मनमानी JS को मजबूर कर सकता है?"
असली सवाल यह है:
क्या एप्लिकेशन हमलावर-नियंत्रित स्क्रिप्ट-युक्त सामग्री संग्रहीत करता है और बाद में इसे अन्य उपयोगकर्ताओं को एक भरोसेमंद इंटरैक्शन पथ के रूप में प्रस्तुत करता है?
कमजोर संस्करणों में, इसने ऐसा किया।
यह संग्रहीत XSS है।
शोषण पथ सीधा था:
इसने उच्च-विशेषाधिकार वाले उपयोगकर्ताओं को भी यथार्थवादी लक्ष्य बना दिया।
यदि एक कार्यक्षेत्र मालिक, व्यवस्थापक, या व्यापक रूप से भरोसेमंद संपादक ने हमलावर-नियंत्रित सामग्री देखी और अटैचमेंट क्रिया पर क्लिक किया, तो हमलावर की स्क्रिप्ट उस अधिक विशेषाधिकार प्राप्त सत्र संदर्भ में चलेगी।
यह महत्वपूर्ण व्यावहारिक बिंदु है:
हमलावर की विशेषाधिकार आवश्यकता केवल कम थी। पीड़ित का विशेषाधिकार स्तर निर्धारित करता है कि XSS सत्र में कितना मूल्य था।
मैंने Docmost v0.70.3 के खिलाफ लाइव समस्या को मान्य किया।