
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 के खिलाफ लाइव समस्या को मान्य किया।
PoC ने केवल सामान्य HTTP अनुरोधों और एप्लिकेशन के अपने पेज API का उपयोग किया।
प्रवाह था:
format: "json" और एक अटैचमेंट नोड जिसका url एक javascript: पेलोड है, के साथ POST /api/pages/update भेजें।POST /api/pages/info के माध्यम से पेज वापस अनुरोध करें।href अभी भी javascript:... है।न्यूनतम दुर्भावनापूर्ण सामग्री थी:
{
"pageId": "<pageId>",
"content": {
"type": "doc",
"content": [
{
"type": "attachment",
"attrs": {
"url": "javascript:alert(document.domain)",
"name": "policy.pdf",
"mime": "application/pdf",
"size": 1
}
}
]
},
"operation": "replace",
"format": "json"
}
मेरे परीक्षण से देखा गया लाइव परिणाम था:
019d18cf-4212-70b0-894a-fe20080fb0f1 थीPOST /api/pages/info ने संग्रहीत JSON लौटाया जिसमें:"url": "javascript:alert(document.domain)"
POST /api/pages/info format: "html" के साथ निम्नलिखित HTML लौटाया:<div data-type="attachment" data-attachment-url="javascript:alert(document.domain)" data-attachment-name="policy.pdf" data-attachment-mime="application/pdf" data-attachment-size="1"><a href="javascript:alert(document.domain)" class="attachment" target="blank">policy.pdf</a></div>
वह HTML प्रतिक्रिया महत्वपूर्ण प्रमाण है।
मुझे एक अस्पष्ट दावे पर निर्भर नहीं रहना पड़ा कि "ब्राउज़र कुछ दिलचस्प कर सकता है।"
एप्लिकेशन ने स्वयं सटीक निष्पादन योग्य सिंक रेंडर किया।
एक बार जब कोई उपयोगकर्ता उस अटैचमेंट लिंक/आइकन पर क्लिक करता है, तो ब्राउज़र javascript: URL को उस पृष्ठ के ओरिजिन में निष्पादित करता है जिसने इसे बनाया।
एडिटर-संचालित XSS के लिए, अकेले स्क्रीनशॉट कमजोर सबूत हैं।
वे लक्षण दिखाते हैं, सीमा विफलता नहीं।
यही कारण है कि मैंने PoC को दो स्पष्ट जाँच बिंदुओं के आसपास संरचित किया:
भंडारण प्रमाण ने दिखाया कि सर्वर ने खतरनाक स्कीम को स्वीकार और संरक्षित किया।
रेंडर किए गए सिंक प्रमाण ने दिखाया कि एप्लिकेशन ने उस संग्रहीत मान को वापस इसमें बदल दिया:
<a href="javascript:...">
यह विभाजन मायने रखता है।
यदि कोई उत्पाद खतरनाक इनपुट संग्रहीत करता है लेकिन प्रत्येक सिंक से पहले इसे निष्क्रिय कर देता है, तो आपके पास एक सख्ती अंतर हो सकता है लेकिन जरूरी नहीं कि एक लाइव XSS हो।
यदि उत्पाद खतरनाक इनपुट संग्रहीत करता है और बाद में इसे एक वास्तविक निष्पादन सिंक में रेंडर करता है, तो आपके पास पूर्ण भेद्यता श्रृंखला है।
यहाँ यही हुआ।
फिक्स v0.71.0 में भेजा गया और अटैचमेंट URL पर URL स्वच्छता लागू करके रेंडर किए गए शोषण पथ को संबोधित किया।
अटैचमेंट एक्सटेंशन अब sanitizeUrl आयात और उपयोग करता है, जिसमें शामिल है:
data-attachment-url को स्वच्छ करनाdata-attachment-url को स्वच्छ करनाhref को स्वच्छ करनाअवधारणात्मक रूप से, पैच ने अटैचमेंट नोड को इससे बदल दिया:
इसमें:
क्लाइंट-साइड हेल्पर getFileUrl() को भी अपडेट किया गया ताकि अज्ञात स्कीम अब अछूती न गुजरें।
पैच किए गए संस्करण में, फ़ॉलबैक पथ sanitizeUrl(src) लौटाता है, न कि src को ज्यों का त्यों।
यह फिक्स का एक महत्वपूर्ण हिस्सा है क्योंकि कमजोर डिज़ाइन में दो प्रबलित समस्याएँ थीं:
href रेंडर कियापैच ने दोनों धारणाओं को हटा दिया।
यह लाइव XSS पथ के लिए एक अच्छा फिक्स था क्योंकि इसने अटैचमेंट URL हैंडलिंग को एडिटर के बाकी सुरक्षा मॉडल के साथ संरेखित किया।
हालांकि, अभी भी एक व्यापक सख्ती सबक है:
क्लाइंट-साइड या रेंडर-टाइम स्वच्छता यहाँ आवश्यक है, लेकिन पेज बनाने/अपडेट के दौरान खतरनाक स्कीम का सर्वर-साइड अस्वीकरण एक और भी मजबूत अपरिवर्तनीय होगा।
सबसे सुरक्षित दीर्घकालिक मॉडल है:
रिच-कंटेंट सिस्टम में गहराई में रक्षा मायने रखती है।
दीर्घकालिक कवरेज के लिए, ये वे मामले हैं जो सबसे अधिक मायने रखते हैं:
attachment.attrs.url = "javascript:..."data-attachment-url="javascript:..."href="javascript:..." उत्सर्जित नहीं करना चाहिए/api/files/... और /files/... सामान्य रूप से काम करते रहना चाहिएमुख्य बिंदु निरंतरता है।
यदि सामान्य लिंक को स्वच्छ किया जाता है लेकिन कस्टम URL-युक्त नोड को नहीं, तो एडिटर के पास वास्तव में एक एकल URL सुरक्षा नीति नहीं है।
इसमें टुकड़े हैं, और टुकड़े वही हैं जहाँ XSS बग रहते हैं।
प्रकाशित सलाहकार ने इस समस्या को वर्गीकृत किया:
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
यह 7.6 / उच्च पर आता है।
यह एक बचाव योग्य वर्गीकरण है।
महत्वपूर्ण गुण हैं:
उपयोगकर्ता इंटरैक्शन आवश्यक बना हुआ है क्योंकि पीड़ित को अटैचमेंट लिंक/आइकन सक्रिय करना होता है।
यही कारण है कि UI:R सही है।
लेकिन एक बार जब वह इंटरैक्शन होता है, तो सुरक्षा सीमा बहुत पहले ही विफल हो चुकी होती है: एप्लिकेशन ने एक खतरनाक स्कीम संग्रहीत की और इसे एक निष्पादन सिंक में वापस रेंडर किया।
मैंने समस्या की निजी रूप से GitHub सुरक्षा सलाहकार के माध्यम से रिपोर्ट की:
समस्या को स्वीकार किया गया, CVE-2026-34212 सौंपा गया, और 14 अप्रैल, 2026 को प्रकाशित किया गया।
सार्वजनिक सलाहकार वर्तमान में सूचीबद्ध करता है:
0.70.30.71.0मेरा लाइव सत्यापन v0.70.3 पर किया गया था, जो प्रकाशित कमजोर संस्करण से मेल खाता है।
यहाँ मुख्य सबक केवल "URL को स्वच्छ करें" नहीं है।
हर कोई पहले से ही यह जानता है।
अधिक दिलचस्प सबक यह है:
यदि किसी एप्लिकेशन में एक सुरक्षित URL-युक्त नोड प्रकार और एक असुरक्षित URL-युक्त नोड प्रकार है, तो असुरक्षित वास्तविक नीति है।
रिच-टेक्स्ट सिस्टम अक्सर कस्टम एक्सटेंशन को सुरक्षा समीक्षा की तुलना में तेज़ी से जमा करते हैं।
यह ठीक इस प्रकार की विषमता पैदा करता है:
यह बग यह भी दिखाता है कि स्कीमा सत्यापन पर्याप्त क्यों नहीं है।
jsonToNode() ने सत्यापित किया कि सामग्री संरचनात्मक रूप से वैध ProseMirror डेटा थी।
इसने यह साबित नहीं किया कि सामग्री रेंडर करने के लिए सुरक्षित थी।
वे अलग-अलग प्रश्न हैं।
सुरक्षा समीक्षा तब बहुत तेज हो जाती है जब आप उन प्रश्नों को अलग रखते हैं:
अटैचमेंट नोड पहले प्रश्न में पास हुआ और तीसरे में विफल रहा।
इस तरह संग्रहीत सामग्री बग अन्यथा अच्छी तरह से संरचित एडिटर पाइपलाइनों के अंदर जीवित रहते हैं।
data-attachment-url और एंकर href को सीधे हमलावर-नियंत्रित इनपुट से रेंडर किया।getFileUrl() ने अज्ञात स्कीम को अपरिवर्तित लौटाया।javascript: को अवरुद्ध किया, लेकिन अटैचमेंट नोड ने ऐसा नहीं किया।v0.71.0 में फिक्स ने अटैचमेंट नोड और क्लाइंट फ़ॉलबैक पथ में sanitizeUrl हैंडलिंग जोड़ी।यह भेद्यता ब्राउज़र की विचित्रता के बारे में नहीं थी।
यह एक कस्टम सामग्री नोड के बारे में था जिसने एप्लिकेशन के अपने URL सुरक्षा अनुमानों को दरकिनार कर दिया।
Docmost ने एक हमलावर-नियंत्रित अटैचमेंट URL स्वीकार किया, इसे भंडारण के माध्यम से संरक्षित किया, और फिर इसे एप्लिकेशन ओरिजिन में एक जीवित एंकर में वापस रेंडर किया।
यही कारण है कि यह CVE-2026-34212 बन गया।
v0.71.0 में पैच ने सक्रिय XSS पथ को साफ-सुथरा बंद कर दिया, लेकिन व्यापक सबक वह है जिसे रखना चाहिए:
एडिटर-भारी अनुप्रयोगों में, प्रत्येक कस्टम नोड जो URL ले जा सकता है, अपनी स्वयं की सुरक्षा सीमा है, और इसकी समीक्षा एक के रूप में की जानी चाहिए।