एक लर्निंग प्लेटफॉर्म जो छात्रों को MongoDB क्वेरीज़ लिखने की अनुमति देता है, उसने Node.js के vm मॉड्यूल को हटा दिया है और अब निष्पादन (execution) से पहले हर क्वेरी को एक एब्स्ट्रैक्ट सिंटैक्स ट्री (AST) में पार्स करता है। यह बदलाव उस सैंडबॉक्स को हटा देता है जिसे तोड़ा जा सकता था, जिससे बैकएंड को उस मनमाने कोड (arbitrary code) से बचाया जा सके जिसे एक गलत तरीके से बनाई गई स्ट्रिंग इंजेक्ट कर सकती है।

मूल सैंडबॉक्स क्यों विफल रहा

पहले कार्यान्वयन (implementation) में एक लाइव डेटाबेस हैंडल को vm.runInContext कॉल में लपेटा गया था और रेगुलर एक्सप्रेशन (regular expression) के साथ उपयोगकर्ताओं को प्रतिबंधित करने की कोशिश की गई थी। इसने केवल find या aggregate जैसे मेथड नामों की अनुमति दी; अन्य सभी आइडेंटिफायर को फ़िल्टर किया जाना था।

दो कमियों ने उस दृष्टिकोण को असुरक्षित बना दिया:

  • Regex फ़िल्टरिंग को बायपास किया जा सकता है। JavaScript कोड को किसी भी प्रॉपर्टी तक पहुँचने के लिए ब्रैकेट नोटेशन (obj["constructor"]) का उपयोग करने की अनुमति देता है। एक हमलावर Function कंस्ट्रक्टर प्राप्त कर सकता है, एक नया फंक्शन बना सकता है, और अपनी पसंद का कोई भी कोड चला सकता है। रेगुलर एक्सप्रेशन कभी भी अंतर्निहित प्रॉपर्टी एक्सेस को नहीं देख पाता क्योंकि सोर्स को अनगिनत तरीकों से फिर से लिखा जा सकता है।
  • vm कोई सुरक्षा सीमा (security boundary) नहीं है। Node के दस्तावेज़ों में कहा गया है कि vm केवल global ऑब्जेक्ट को अलग करता है, पूरे प्रोसेस को नहीं। सैंडबॉक्स में एक लाइव डेटाबेस कनेक्शन इंजेक्ट करके, कॉन्टेक्स्ट के अंदर का कोड उस कनेक्शन पर किसी भी मेथड को कॉल करने की क्षमता बनाए रखता है, जिसमें डेटा लिखने या हटाने वाले मेथड भी शामिल हैं। सैंडबॉक्स कोड को होस्ट प्रोसेस को प्रभावित करने से नहीं रोक सका।

AST-आधारित समाधान

टीम ने कोड निष्पादन को स्टैटिक एनालिसिस (static analysis) से बदल दिया है। क्वेरी स्ट्रिंग्स अब acorn पार्सर में जाती हैं, जो एक AST तैयार करता है—जो कोड की सिंटैक्टिक संरचना का एक ट्री रिप्रेजेंटेशन है। AST की जांच एक सख्त व्हाइटलिस्ट के विरुद्ध नोड-दर-नोड की जाती है:

  • Literals, arrays और objects की अनुमति केवल तभी दी जाती है जब वे सादे मानों (plain values) के रूप में दिखाई देते हैं।
  • Method calls को एक पूर्व-निर्धारित सेट (find, sort, limit, आदि) तक सीमित रखा गया है। किसी भी अन्य कॉल को अस्वीकार कर दिया जाता है।
  • Computed property access (जैसे, obj[expr]) या कोई भी नोड टाइप जो स्पष्ट रूप से सूचीबद्ध नहीं है, वह तुरंत विफलता (failure) का कारण बनता है।

क्योंकि पार्सर कच्चे टेक्स्ट (raw text) के बजाय ट्री पर काम करता है, इसलिए इसे वैकल्पिक स्पेलिंग या ब्रैकेट-नोटेशन के तरीकों से मूर्ख नहीं बनाया जा सकता है। एक कंस्ट्रक्टर चेन जो रेगुलर एक्सप्रेशन से बच निकलती, वह एक अज्ञात नोड के रूप में दिखाई देती है और किसी भी कोड के चलने से पहले ही अस्वीकार कर दी जाती है।

सुरक्षा के लिए इसका क्या अर्थ है

नया डिज़ाइन "डिनाइ बाय डिफॉल्ट" (deny by default) दर्शन का पालन करता है:

  • यह परिभाषित करें कि क्या अनुमत है, न कि यह कि क्या वर्जित है। टेक्स्टुअल अलाउ-लिस्टिंग (allow-listing) व्यापक नहीं हो सकती; एक AST में नोड प्रकारों का एक सीमित सेट होता है, जिससे व्यापक जांच करना संभव हो जाता है।
  • सैंडबॉक्स के अंदर कभी भी लाइव रिसोर्स को एक्सपोज़ न करें। एक आइसोलेटेड कॉन्टेक्स्ट में डेटाबेस हैंडल पास करने से सैंडबॉक्स किए गए कोड को बैकएंड तक सीधी पहुँच मिल जाती है। पार्सर दृष्टिकोण यूजर कोड को कभी भी लाइव ऑब्जेक्ट नहीं सौंपता; यह केवल क्वेरी के इरादे (intent) को निकालता है।
  • स्ट्रक्चर को वैलिडेट करें, फिर सुरक्षित रूप से निष्पादित करें। एक बार जब AST वैलिडेशन पास कर लेता है, तो प्लेटफॉर्म अपने स्वयं के भरोसेमंद कोड पाथ का उपयोग करके अनुमत कॉल्स को वास्तविक MongoDB ड्राइवर मेथड में बदल देता है।

आगे क्या ध्यान रखें

  • अपने कोडबेस में eval, new Function, या vm के किसी भी उपयोग का ऑडिट करें। यहाँ तक कि व्हाइटलिस्ट को भी JavaScript की गतिशील प्रकृति (dynamic nature) द्वारा विफल किया जा सकता है।
  • जहाँ भी संभव हो, यूजर-जेनरेटेड कोड के लिए AST पार्सिंग अपनाएं। acorn, esprima, या babel-parser जैसी लाइब्रेरीज़ इस रूपांतरण को सरल बनाती हैं।
  • लाइव ऑब्जेक्ट्स के एक्सपोज़र को सीमित करें। यदि डेटाबेस कनेक्शन, फ़ाइल हैंडल, या नेटवर्क सॉकेट तक पहुँचना आवश्यक है, तो इसे एक प्रॉक्सी में लपेटें (wrap) जो केवल उन्हीं न्यूनतम मेथड को एक्सपोज़ करे जिन्हें आप अनुमति देना चाहते हैं।
  • एज केसेस (edge cases) की टेस्टिंग को ऑटोमेट करें। ऐसी क्वेरीज़ जेनरेट करें जो ब्रैकेट नोटेशन, कंप्यूटेड कीज़, या प्रोटोटाइप मैनिपुलेशन का उपयोग करती हों, ताकि यह सत्यापित किया जा सके कि आपका पार्सर उन्हें अस्वीकार कर देता है।

निष्कर्ष

सैंडबॉक्स के रूप में vm.runInContext पर भरोसा करना सुरक्षा का एक झूठा अहसास देता है; रेगुलर एक्सप्रेशन फ़िल्टर JavaScript के लचीले सिंटैक्स को कवर नहीं कर सकते, और सैंडबॉक्स प्रोसेस रिसोर्स को अलग नहीं करता है। यूजर इनपुट को AST में पार्स करना और केवल उन्हीं नोड्स को व्हाइटलिस्ट करना जिन्हें आप समझते हैं, एक ठोस और बनाए रखने योग्य बाधा प्रदान करता है जो दुर्भावनापूर्ण कोड को चलने से पहले ही रोक देता है। यदि आपका प्लेटफॉर्म उपयोगकर्ताओं को कोड लिखने की अनुमति देता है, तो आज ही eval-शैली के निष्पादन को स्टैटिक एनालिसिस से बदलें।