दस्तावेज़ीकरण
/

समस्या निवारण

/

eval और new Function के साथ VM ऑब्फ़स्केशन

eval और new Function के साथ VM ऑब्फ़स्केशन

VM ऑब्फ़स्केशन डायनामिक कोड निर्माण (direct eval और Function कंस्ट्रक्टर) को कैसे संभालता है, क्या बाइटकोड में बदला जाता है और क्या छोड़ दिया जाता है, ऑब्फ़स्केटर कौन-सी चेतावनियाँ देता है, और रनटाइम पर ReferenceError का निदान कैसे करें।

यह क्यों मायने रखता है

VM ऑब्फ़स्केशन फ़ंक्शन बॉडी को बाइटकोड में कंपाइल करता है, जिसे रनटाइम के अंदर मौजूद इंटरप्रेटर के ज़रिए डिस्पैच किया जाता है। आसपास के स्कोप के आइडेंटिफ़ायर के नाम भी बदले जाते हैं। ये दोनों बदलाव उस कोड के साथ ठीक से काम नहीं करते जो रनटाइम पर किसी स्ट्रिंग से बनाया जाता है: eval(s), new Function(...s) और Function(...s)। अगर रनटाइम पर बना कोड किसी ऐसे आइडेंटिफ़ायर को रेफ़र करता है जिसका नाम ऑब्फ़स्केटर ने बदल दिया है, तो जनरेट किया गया फ़ंक्शन पहली बार चलते ही आपको Uncaught ReferenceError: <renamed-name> is not defined मिलता है।

ऑब्फ़स्केटर हर पैटर्न को अलग तरह से संभालता है। नीचे दी गई तालिका संक्षिप्त रूप है; इस पेज का बाकी हिस्सा हर पंक्ति को समझाता है।

ऑब्फ़स्केटर क्या करता है, एक नज़र में

आपके सोर्स में पैटर्नक्या होता है
eval('literal string') (बॉडी एक स्ट्रिंग लिटरल है)सही चलता है। इस कॉल वाला फ़ंक्शन, और उसके अंदर परिभाषित हर फ़ंक्शन, VM बाइटकोडिंग खो देता है (direct eval आसपास के लोकल वेरिएबल्स पढ़ता है, जिन्हें फ़ंक्शन के बाइटकोड में कंपाइल होने के बाद VM सुरक्षित नहीं रखता)।
eval(dynamicExpression)रनटाइम पर ReferenceError के साथ क्रैश हो सकता है। इस कॉल वाला फ़ंक्शन, और उसके अंदर परिभाषित हर फ़ंक्शन, भी VM बाइटकोडिंग खो देता है।
(0, eval)(s) / window.eval(s) (indirect)इस कॉल वाला फ़ंक्शन सामान्य रूप से VM-बाइटकोड किया जाता है। Indirect eval ग्लोबल स्कोप में चलता है और आसपास के लोकल वेरिएबल्स नहीं देख सकता, इसलिए नाम बदले गए लोकल्स इसे नहीं तोड़ सकते। यह फिर भी विफल हो सकता है अगर eval किया गया कोड किसी ऐसे ग्लोबल को रेफ़र करे जिसका नाम बदला गया हो या जिसे हटा दिया गया हो।
new Function('a', 'b', 'return a + b') (सभी आर्ग्युमेंट स्ट्रिंग लिटरल हैं)सही चलता है। इस कॉल वाला फ़ंक्शन सामान्य रूप से VM-बाइटकोड किया जाता है।
new Function(dynamicBody) / Function(dynamicBody)रनटाइम पर ReferenceError के साथ क्रैश हो सकता है। इस कॉल वाला फ़ंक्शन, और उसके अंदर परिभाषित हर फ़ंक्शन, भी VM बाइटकोडिंग खो देता है।

कंपाइलर eval?.(code) को भी सावधानी के तौर पर पहचानता है और उसे direct eval की तरह संभालता है। JavaScript इस optional-call रूप को indirect eval के रूप में परिभाषित करती है; कंपाइलर की यह पहचान भाषा के उस व्यवहार को नहीं बदलती।

इनमें से केवल कुछ पैटर्न गैर-घातक चेतावनियाँ देते हैं, और केवल तब जब कॉल किसी फ़ंक्शन के अंदर हो: डायनामिक eval, new Function या Function कॉल DynamicCodeRenameRisk और VMDynamicCodeSkipped दोनों रिपोर्ट करती है, स्टैटिक eval('...') केवल VMDynamicCodeSkipped रिपोर्ट करता है, और indirect eval व पूरी तरह स्टैटिक new Function कुछ भी रिपोर्ट नहीं करते। फ़ाइल के टॉप लेवल पर, किसी भी फ़ंक्शन के बाहर, डायनामिक कॉल कभी रिपोर्ट नहीं की जाती। चेतावनी प्रकारों और CI स्निपेट के लिए नीचे रनटाइम से पहले समस्या का पता लगाना देखें।

यह छूट IIFE के ज़रिए आगे फैलती है। अगर कोई टॉप-लेवल IIFE आपके पूरे बंडल को रैप करता है और उसके अंदर कोई भी फ़ंक्शन डायनामिक eval या new Function का उपयोग करता है, तो पूरा IIFE VM बाइटकोडिंग से छूट जाता है। असर का दायरा सीमित करने वाले IIFE-अनरैपिंग पैटर्न के लिए Direct eval का व्यवहार देखें।

स्टैटिक और डायनामिक को अलग तरह से क्यों संभाला जाता है

eval(s) जिस फ़ंक्शन में कॉल होता है, उसके लोकल वेरिएबल्स पढ़ और लिख सकता है। जब s एक स्ट्रिंग लिटरल हो, तो ऑब्फ़स्केटर ऑब्फ़स्केशन के समय बॉडी को पार्स कर सकता है और आसपास के कोड के साथ मेल रखते हुए आइडेंटिफ़ायर के नाम बदल सकता है। जब s एक डायनामिक एक्सप्रेशन हो, तो पार्सिंग केवल रनटाइम पर होती है, और तब तक आइडेंटिफ़ायर के नाम बदले जा चुके होते हैं, इसलिए रनटाइम पर बना सोर्स ऐसे पुराने नामों को रेफ़र करता है जो अब मौजूद नहीं हैं।

new Function(s) और indirect eval अलग तरह से काम करते हैं: बॉडी हमेशा ग्लोबल स्कोप में चलती है, जहाँ केवल ग्लोबल वेरिएबल्स तक पहुँच होती है, कॉल के आसपास के लोकल्स तक कभी नहीं। ये नाम बदले गए लोकल्स पर निर्भर नहीं हो सकते, लेकिन फिर भी विफल हो सकते हैं अगर ये ऐसे ग्लोबल्स को रेफ़र करें जिनका नाम बदला गया हो या जिन्हें हटा दिया गया हो, या अगर आप बॉडी में किसी नाम बदले गए आइडेंटिफ़ायर को जोड़कर उसे बनाते हैं (जैसे किसी ऐसे फ़ंक्शन के func.toString() से, जिसके अंदरूनी हिस्से को ऑब्फ़स्केटर ने दोबारा लिखा है)।

केवल अपने ही पैरामीटर्स का उपयोग करने वाली स्टैटिक बॉडी, जैसे new Function('a', 'b', 'return a + b'), की ऐसी कोई निर्भरता नहीं होती: बॉडी एक सामान्य स्ट्रिंग है जिसे रीनेमर कभी नहीं जाँचता। ऑब्फ़स्केटर कॉल को जस का तस छोड़ देता है और आसपास का फ़ंक्शन VM बाइटकोडिंग के लिए योग्य बना रहता है। केवल eval सिंटैक्स बदलने से मनमाना डायनामिक कोड सुरक्षित नहीं हो जाता; चेतावनियों की समीक्षा करें और अंतिम बंडल का परीक्षण करें।

रनटाइम पर दिखने वाली एरर

आम लक्षण है डायनामिक रूप से बनाए गए फ़ंक्शन के पहली बार चलने पर ReferenceError:

Text

यहाँ TU एक नाम बदला गया आइडेंटिफ़ायर है जिसे ऑब्फ़स्केटर ने बंडल के IIFE स्कोप के अंदर जोड़ा है। डायनामिक eval / Function-कंस्ट्रक्टर कॉल ऐसी बॉडी को eval करती है जो इसे रेफ़र करती है, लेकिन बॉडी ऐसे स्कोप में चलती है जहाँ TU परिभाषित नहीं है।

रनटाइम से पहले समस्या का पता लगाना

ऑब्फ़स्केटर API के ज़रिए गैर-घातक चेतावनियाँ देता है, ताकि आप शिप करने से पहले CI में इनमें से कुछ पैटर्न्स को पकड़ सकें। दो चेतावनी प्रकार प्रासंगिक हैं, और दोनों केवल किसी फ़ंक्शन के अंदर की कॉल्स के लिए रिपोर्ट किए जाते हैं:

  • DynamicCodeRenameRisk: कोई फ़ंक्शन रनटाइम पर किसी स्ट्रिंग से कोड बनाता है: direct eval, या ऐसी new Function / Function कॉल जिसकी बॉडी स्टैटिक नहीं है, या <script> या Worker में डाला गया fn.toString()। यही वह चेतावनी है जो ReferenceError का पूर्वानुमान देती है।
  • VMDynamicCodeSkipped: किसी फ़ंक्शन और उसके अंदर परिभाषित हर फ़ंक्शन के लिए VM बाइटकोडिंग छोड़ दी गई, क्योंकि उसमें direct eval या डायनामिक new Function / Function कॉल है। यह सुरक्षित स्टैटिक eval('literal') के लिए भी आती है, इसलिए यह रनटाइम क्रैश के बजाय खोई हुई VM सुरक्षा का संकेत देती है। इसमें फ़ंक्शन का नाम (अगर उपलब्ध हो) और छूट का कारण बनी संरचना शामिल होती है।

javascript-obfuscator npm पैकेज अपने परिणाम पर चेतावनियाँ उपलब्ध नहीं कराता, इसलिए CI गेट उन्हें API रिस्पॉन्स से पढ़ता है: result और chunk_end संदेशों में एक warnings ऐरे होता है। नीचे दिया गया उदाहरण readObfuscationResponse() का उपयोग करता है, जो API संदर्भ का स्ट्रीम रीडर है, और बिल्ड को केवल DynamicCodeRenameRisk पर विफल करता है; अगर किसी फ़ंक्शन पर VM सुरक्षा खोने से भी रिलीज़ रुकनी चाहिए, तो फ़िल्टर में VMDynamicCodeSkipped जोड़ें।

Code

अगर आपके बिल्ड में कोई चेतावनी प्रकार अपेक्षित है और आप उसे CI में फ़िल्टर करने के बजाय स्रोत पर ही शांत करना चाहते हैं, तो warnings विकल्प (v7.8.0+) नियंत्रित करता है कि कौन-सी चेतावनियाँ दी जाएँ: 'none' सब कुछ दबा देता है, और { VMDynamicCodeSkipped: false } जैसा प्रति-प्रकार मैप बाकी को रखते हुए केवल एक प्रकार को शांत करता है।

वैकल्पिक उपाय

  • indirect eval ((0, eval)(s)) पर स्विच करें

    यह केवल eval वाले मामले में उपयोगी है। Indirect eval ग्लोबल स्कोप में चलता है, इसलिए यह आसपास के लोकल वेरिएबल्स नहीं देख सकता, और इसी कारण नाम बदले गए लोकल्स को भी रेफ़र नहीं कर सकता। कॉल वाला फ़ंक्शन VM-बाइटकोड बना रहता है। eval किया गया कोड फिर भी उन ग्लोबल्स पर निर्भर नहीं होना चाहिए जिनके नाम ऑब्फ़स्केटर बदलता है।

  • बॉडी को पूरी तरह स्टैटिक बनाएँ

    new Function के लिए, अगर आप बॉडी को बिना इंटरपोलेशन वाले एक ही स्ट्रिंग लिटरल / टेम्पलेट लिटरल के रूप में लिख सकते हैं, तो कॉल में नाम बदलने का कोई जोखिम नहीं रहता और उसके आसपास का फ़ंक्शन फिर भी बाइटकोड किया जाता है। new Function('a', 'b', 'return a + b') ठीक है; new Function('return ' + expr) नहीं।

  • कॉल को उसके अपने टॉप-लेवल फ़ंक्शन में ले जाएँ, और IIFE को अनरैप करें

    हर टॉप-लेवल फ़ंक्शन की जाँच अलग से होती है। डायनामिक-कोड कॉल को उसके अपने टॉप-लेवल फ़ंक्शन में ले जाने का मतलब है कि केवल वही एक फ़ंक्शन VM बाइटकोडिंग खोता है, न कि छूट आपके पूरे बंडल को रैप करने वाले टॉप-लेवल IIFE में फैल जाती है।

  • vmTargetFunctionsMode: 'comment' पर स्विच करें

    Opt-in मोड: केवल /* javascript-obfuscator:vm */ से चिह्नित फ़ंक्शन ही बाइटकोड किए जाते हैं। डायनामिक-कोड कॉल वाले फ़ंक्शन को चिह्नित न करें, और बाकी को चिह्नित करें। इन कमेंट्स को ऑब्फ़स्केशन चरण तक बनाए रखें। देखें फ़ंक्शन्स को लक्षित करना।

  • vmForceCompileDynamicCode: true से छूट को ओवरराइड करें (v6.14.0+)

    यह आख़िरी उपाय है। चालू होने पर ऑब्फ़स्केटर आसपास के फ़ंक्शन को फिर भी बाइटकोड करता है और VMDynamicCodeSkipped चेतावनी को दबा देता है। यह स्कोप या नाम बदले गए आइडेंटिफ़ायर की निर्भरताओं को ठीक नहीं कर सकता: इसका उपयोग केवल तभी करें जब आप गारंटी दे सकें कि रनटाइम पर बनी बॉडी कभी ऐसे आइडेंटिफ़ायर को रेफ़र नहीं करती जिसका नाम ऑब्फ़स्केटर बदलता है; वरना आप साफ़ ऑब्फ़स्केशन के बदले रनटाइम पर ReferenceError पाएँगे। DynamicCodeRenameRisk फिर भी आती है, ताकि CI उस पर रोक लगाना जारी रख सके। डैशबोर्ड में यह VM सेक्शन के ओवरराइड्स समूह के अंतर्गत "Force Compile Dynamic Code" स्विच है।

संबंधित पेज