
TC39 proposal for mitigating prototype pollution
लेखक: Santiago Díaz (Google)
चैंपियन: Shu-yu Guo (Google)
चरण: 1
freeze, seal और preventExtensions से जुड़ी समस्याएँ
यह प्रस्ताव एक भाषा-स्तरीय भेद्यता, जिसे प्रोटोटाइप पोल्यूशन के रूप में जाना जाता है, को एक ऐसे तंत्र से कम करना चाहता है जो फ्रीज़ प्रिमिटिव का पूरक हो, साथ ही एक ऐसा तंत्र जो अधिकांश कोड बेस को इसके अनुकूल बना सके। यह एक ऑप्ट-इन सुविधा का वर्णन करता है जो प्रोटोटाइप को केवल रिफ्लेक्शन APIs के माध्यम से उपलब्ध कराती है। ऐसा करने से, obj[key] कथन अब प्रोटोटाइप तक नहीं पहुँच सकता। इस सुविधा के अनुकूल कोड बेस प्रोटोटाइप का उपयोग करने के तरीके में अधिक इरादतन होते हैं।
PP भेद्यताएँ हमलावरों को उन ऑब्जेक्ट में हेरफेर करने की अनुमति देती हैं जिन्हें वे नियंत्रित नहीं करते या जिन तक उनकी रनटाइम पर पहुँच नहीं होती। यह 'दूरी पर रहस्यमय प्रभाव' प्रिमिटिव अन्य ऑब्जेक्ट का आकार बदलने और उनके गुणों को ओवरराइड करने के लिए इस्तेमाल किया जा सकता है, जिससे रनटाइम में ऑब्जेक्ट दूषित हो जाते हैं।
दूषित ऑब्जेक्ट उस कोड की अंतर्निहित मान्यताओं को अमान्य कर देते हैं जो अन्यथा सुरक्षित/सही होता, और JS कोड बेस में मनमाना कोड निष्पादन और कई अन्य सुरक्षा समस्याएँ पैदा कर सकते हैं। प्रोटोटाइप पोल्यूशन बग अक्सर वेब अनुप्रयोगों में प्रकट होते हैं, लेकिन यह गैर-वेब JS रनटाइम को भी प्रभावित करते हैं।
JS में ऑब्जेक्ट गुण किसी भी कोड द्वारा लिखने योग्य होते हैं जो उन्हें संदर्भित कर सकता है। विशेष रूप से, यदि कई ऑब्जेक्ट किसी साझा गुण पर निर्भर करते हैं, तो उनमें से कोई भी अन्य सभी पर परिवर्तन प्रभावी कर सकता है।
PP की एक विशेष विशेषता यह है कि यह एक केवल-डेटा हमला है, जो पूरी तरह से डेटा के माध्यम से कोड निष्पादन प्राप्त करने की अनुमति देता है। उदाहरण के लिए, निम्नलिखित भेद्य कोड और उसका संगत एक्सप्लॉइट देखें:
// source is attacker-controlled
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if(target[key] === undefined) {
target[key] = {};
}
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// User input comes as a string
const userSuppliedObj = JSON.parse('{"__proto__": {"polluted": true}}');
// Trigger prototype pollution
merge({}, userSuppliedObj);
// Create a brand new object
const newObj = {};
// Has polluted property
console.log(newObj.polluted); // true
ध्यान दें कि यह एक्सप्लॉइट बिना कोई बाहरी कोड इंजेक्ट किए नए ऑब्जेक्ट के निर्माण को दूषित करने में सक्षम है।
इस विशेष गुण के कारण, कोड निष्पादन समस्याओं के विरुद्ध आधुनिक शमन उपाय - जैसे Content Security Policy या Trusted Types - PP से सुरक्षा में कम पड़ जाते हैं, क्योंकि वे कोड उत्पत्ति को लागू करने पर केंद्रित होते हैं।
ध्यान दें कि केवल-डेटा हमले उन स्थितियों में प्रासंगिक हैं जहाँ VM पर चलने वाला कोड विश्वसनीय है और मनमाना कोड निष्पादन का सुरक्षा पर प्रभाव पड़ता है।
freeze, seal और preventExtensions से जुड़ी समस्याएँमौजूदा फ्रीज़िंग प्रिमिटिव में महत्वपूर्ण डिज़ाइन समस्याएँ हैं जो उन्हें व्यापक रूप से अपनाए जाने की संभावना कम बनाती हैं। वे विशेषज्ञ उपयोगकर्ताओं के लिए उपयोगी हो सकते हैं, लेकिन अधिकांश डेवलपर्स द्वारा तैनात किए जाने के लिए उपयुक्त नहीं हैं, जो उचित रूप से प्रोटोटाइप के परिवर्तनशील (mutable) होने की अपेक्षा करते हैं:
फ्रीज़ APIs ओवरराइड मिस्टेक और अन्य असंगतियों से ग्रस्त हैं, जो मौजूदा कोड बेस में बग पैदा करती हैं, जिससे वे थ्रो करते हैं या इससे बुरा, स्लॉपी मोड में चुपचाप विफल हो जाते हैं। ओवरराइड मिस्टेक की पिछली जाँच ने निष्कर्ष निकाला था कि यह स्ट्रिक्ट मोड में ~10% कोड बेस और स्लॉपी मोड में 20% पर ट्रिगर होता है। यह जाँच कुछ समय बाद बंद कर दी गई थी।
फ्रीज़ APIs डेवलपर्स को यह जानने की भारी ज़िम्मेदारी देती हैं कि सुरक्षित कोड बेस बनाए रखने के लिए कौन से प्रोटोटाइप फ्रीज़ किए जाने चाहिए, यह मानते हुए कि डेवलपर्स सुरक्षा विशेषज्ञ हैं। ये APIs सुरक्षा का क्या (what) वर्णन करती हैं, लेकिन कैसे (how) नहीं। Object को फ्रीज़ करना निश्चित रूप से पर्याप्त नहीं है, क्योंकि कई एक्सप्लॉइट Array का दुरुपयोग करते हैं। Error, Date, Reflect या Proxy का क्या? या भविष्य के अंतर्निहित प्रकार? फ्रीज़ APIs इन सवालों का कोई उत्तर नहीं देती हैं।
फ्रीज़ APIs एक स्थिर फ्रीज़िंग बिंदु मान लेती हैं: रनटाइम पर एक निश्चित क्षण जब प्रोटोटाइप स्थिर हो चुके होते हैं और फ्रीज़ किए जा सकते हैं। व्यवहार में, यह बिंदु अस्थिर होता है और सक्रिय रूप से विकसित होने वाले कोड बेस में समय के साथ बदलता रहता है। हालाँकि आज कई अनुप्रयोगों में ऐसा बिंदु पाया जा सकता है, नई निर्भरताएँ, पॉलीफिल, कोड संरचना में बदलाव और हॉटस्वैपिंग तथा डेवलपर टूल जैसी उन्नत सुविधाएँ फ्रीज़िंग बिंदुओं को एक गतिशील लक्ष्य बना देती हैं।
फ्रीज़ APIs पूरी प्रोटोटाइप श्रृंखला की रक्षा नहीं कर सकतीं। JS में, ऑब्जेक्ट को किसी भी समय प्रोटोटाइप श्रृंखला में जोड़ा या हटाया जा सकता है। पूरी श्रृंखला की सुरक्षा के लिए, किसी को श्रृंखला में जोड़े गए ऑब्जेक्ट को फ्रीज़ करना हमेशा याद रखना चाहिए, जो एक त्रुटि-प्रवण प्रक्रिया है। जब वे श्रृंखला से हटा दिए जाते हैं, तो उन्हें फिर से अनफ्रीज़ नहीं किया जा सकता।
संक्षेप में: एक ऐसी सुविधा जो प्रोटोटाइप को केवल रिफ्लेक्शन APIs के लिए उजागर करती है। यदि प्रोटोटाइप __proto__ या prototype जैसे गुणों के माध्यम से उपलब्ध नहीं कराए जाते, तो वे केवल-डेटा समस्याओं के संपर्क में नहीं आते।
इसे एक उदाहरण से बेहतर समझा जा सकता है: obj[one][two] = value कथन obj.__proto__.polluted के माध्यम से PP के प्रति भेद्य है। यदि कोई Object.prototype.__proto__ गुण को हटा देता है, तो वही कथन अब भेद्य नहीं रहता, क्योंकि यह प्रोटोटाइप तक पहुँचने के एकमात्र अन्य तरीके में फिट नहीं बैठ सकता, जो obj.constructor.prototype.polluted है। ध्यान दें कि प्रोटोटाइप को हटाया नहीं जा सकता।
इस प्रस्ताव को रिफ्लेक्शन APIs प्रदान करके और एक नई ऑप्ट-इन एनकैप्सुलेशन सुविधा बनाकर लागू किया जा सकता है जो प्रोटोटाइप गुणों को हटा देती है। प्रत्येक चरण का विवरण निम्नलिखित है।
__proto__ एक विरासत (legacy) गुण नाम है जिसे हटाया जा सकता है, लेकिन इसके पीछे का आंतरिक स्लॉट Object/Reflect.getPrototypeOf के माध्यम से पढ़ा जा सकता है और Object/Reflect.setPrototypeOf के माध्यम से लिखा जा सकता है, जो इस गुण को पहले से चल रहे कोड के लिए सुलभ बनाता रहेगा।
हम prototype के लिए नए APIs बनाने का प्रस्ताव करते हैं, उदाहरण के लिए getClassPrototypeOf और setClassPrototypeOf, जो इस गुण नाम को बिना किसी भी तरह से बदले हटाने की अनुमति देंगे कि यह विशेष गुण कैसे काम करता है और VM को कैसे समर्थन देता है।
रिफ्लेक्शन APIs को पॉलीफिल किया जा सकता है, जो कठोर (hardened) कोड बेस को पुराने संस्करणों सहित सभी ब्राउज़रों में काम करने की अनुमति देता है।
एक नई ऑप्ट-इन 'एनकैप्सुलेशन सुविधा', जिसमें प्रोटोटाइप स्लॉट्स के गेटर और सेटर फ़ंक्शन के लिए कोई गुण नाम नहीं बनाए जाते, जो अब संभव है क्योंकि उन गुणों के संदर्भ इसके बजाय रिफ्लेक्शन APIs का उपयोग कर सकते हैं।
यह सुविधा एक आउट-ऑफ-बैंड फ़्लैग के माध्यम से सक्षम की जाती है:
X-Encapsulate-Prototype: true जैसे HTTP हेडर के माध्यम से--encapsulate-prototype जैसे फ़ीचर फ़्लैग के माध्यम सेजब एनकैप्सुलेशन अक्षम होता है, तो प्रोटोटाइप गुणों और रिफ्लेक्शन APIs दोनों के माध्यम से उपलब्ध होते हैं।
जब एनकैप्सुलेशन सक्षम होता है, तो प्रोटोटाइप केवल रिफ्लेक्शन APIs के माध्यम से उपलब्ध होते हैं, और __proto__ तथा prototype दोनों को हटा दिया जाता है।
एनकैप्सुलेशन में निम्नलिखित स्वचालित रिफैक्टरिंग सुविधा भी शामिल है:
जब एनकैप्सुलेशन सक्षम होता है, तो JS इंजन नए स्रोत कोड को लोड करते समय अपने पार्स चरणों में एक अतिरिक्त चरण सक्षम करते हैं जो प्रोटोटाइप गुणों के सभी डॉट-नोटेशन को उनके रिफ्लेक्शन APIs के कॉल के रूप में पंजीकृत करता है। इस चरण को कुशलतापूर्वक लागू किया जा सकता है और यह तृतीय-पक्ष, ट्रांज़िटिव या गतिशील रूप से लोड की गई निर्भरताओं वाले कोड बेस को एनकैप्सुलेशन के अनुकूल होने की अनुमति देता है।
भविष्य में, यह परिवर्तन prototype को अप्रचलित (deprecated) चिह्नित करने का मार्ग प्रशस्त करेगा।
एनकैप्सुलेशन सक्षम होने पर प्रोटोटाइप गुण केवल undefined हो सकते हैं, लेकिन उन्हें पढ़ने/लिखने के प्रयासों पर वे एक त्रुटि फेंक सकते हैं। इसका अर्थ होगा जल्दी और स्पष्ट रूप से विफल होना, और यह रिफ्लेक्शन APIs/एनकैप्सुलेशन में माइग्रेशन के परीक्षण की अनुमति देगा।
इसका तात्पर्य एक होस्ट हुक का उपयोग करके __proto__ और prototype के गेटर और सेटर को एनकैप्सुलेशन पर सशर्त बनाना है, उसी तरह जैसे Content Security Policy के अंतर्गत eval फ़ंक्शन थ्रो करता है।
वह कोड जो प्रोटोटाइप को संदर्भित करने के लिए कम्प्यूटेड प्रॉपर्टी एक्सेस पर निर्भर करता है, वह एनकैप्सुलेशन या स्वचालित रिफैक्टरिंग के अनुकूल नहीं है। जब भी उनका उपयोग किया जाए, तो उसे स्पष्ट रूप से प्रोटोटाइप को संदर्भित करने के लिए रिफैक्टर किया जाना चाहिए। यह रिफैक्टरिंग वास्तव में कोड को आशय व्यक्त करने योग्य बनाती है, जो खतरनाक पैटर्न को स्थैतिक विश्लेषण के लिए दृश्यमान बनाता है। व्यवहार में, इस विशेषता वाले कोड बेस आमतौर पर रिफ्लेक्शन फ्रेमवर्क, डिबगिंग टूल और अन्य रिफ्लेक्शन-प्रधान उपयोग के मामले होते हैं, जो संभवतः अच्छी तरह जानते हैं कि वे प्रोटोटाइप का उपयोग कैसे करते हैं।
जो कोड बेस कस्टम गुणों को परिभाषित करने के लिए prototype शब्द का उपयोग करते हैं, वे संगत नहीं हैं। ऐसे कोड बेस को एनकैप्सुलेशन के अनुकूल बनाया जा सकता है यदि इस गुण को हमेशा ब्रैकेट नोटेशन के माध्यम से सेट/गेट किया जाए। ऐतिहासिक रूप से और HTTP Archive क्वेरी के आधार पर, इस कारण से असंगत कोड बेस का एक छोटा प्रतिशत मौजूद है।
constructor गुण में कुछ परिवर्तन भी दूरी पर रहस्यमय प्रभाव डाल सकते हैं। अपने शोध के दौरान, हमें इससे प्रभावित कोई व्यावहारिक भेद्यता नहीं मिली।
इस हमले के काम करने के लिए सीमा काफी ऊँची है: PP की तरह, किसी को एक ऐसा एप्लिकेशन ढूँढना होगा जिसमें मनमाने गुणों को लिखने और पढ़ने दोनों के लिए गैजेट हों। लेकिन कंस्ट्रक्टर पोल्यूशन में पढ़ने वाले गैजेट को polluted के बजाय constructor.polluted से पढ़ना चाहिए। यह उपयोगी गैजेट्स की संख्या को नाटकीय रूप से कम कर देता है।
कुछ मिनिमाइज़्ड JS एनकैप्सुलेशन मोड के साथ असंगत हो सकते हैं, क्योंकि स्थैतिक गुण एक्सेस को कम्प्यूटेड एक्सेस में मिनिफाई किया जा सकता है। हमने व्यवहार में इसका अनुमान लगाने के लिए HTTP Archive से क्वेरी की है। निम्नलिखित तालिका दर्शाती है कि डेस्कटॉप ब्राउज़र से क्रॉल किए गए सभी पृष्ठों में, पिछले 12 महीनों में इस व्यवहार वाले पृष्ठ लगातार 1% से नीचे हैं:
Google ने अपने Vulnerability Rewards Program में सबमिट किए गए बगों में एक बढ़ती प्रवृत्ति देखी है: 2020 में 1, 2021 में 3 और 2022 में अब तक 5। हमने अपने आंतरिक शोध में कई और की पहचान की है।
उदाहरण भेद्यताओं में शामिल हैं:
हम उम्मीद करते हैं कि जैसे-जैसे JavaScript अनुप्रयोग अधिक वातावरणों (जैसे Electron, Cloudflare Workers, आदि) में तैनात किए जाएँगे, भेद्य अनुप्रयोगों की संख्या बढ़ेगी। इसलिए, सभी वातावरणों में हमलों को कम करने के लिए एक भाषा-स्तरीय समाधान आवश्यक है।
| तालिका | दस्तावेज़ जो __proto__ या constructor को गतिशील रूप से एक्सेस करते हैं | क्रॉल किए गए दस्तावेज़ों की कुल संख्या | अनुपात |
|---|
| 2023_03_01_desktop | 5,407,936 | 609,469,458 | 0.89% |
| 2023_02_01_desktop | 4,842,383 | 549,089,708 | 0.88% |
| 2023_01_01_desktop | 5,283,826 | 589,519,160 | 0.90% |
| 2022_12_01_desktop | 5,161,471 | 577,073,883 | 0.89% |
| 2022_11_01_desktop | 5,023,169 | 561,726,239 | 0.89% |
| 2022_10_01_desktop | 4,393,377 | 476,880,624 | 0.92% |
| 2022_09_01_desktop | 4,239,257 | 466,278,762 | 0.91% |
| 2022_08_01_desktop | 4,259,814 | 463,784,047 | 0.92% |
| 2022_07_01_desktop | 3,011,137 | 339,468,615 | 0.89% |
| 2022_06_01_desktop | 2,301,317 | 257,501,222 | 0.89% |
| 2022_04_01_desktop | 2,368,577 | 263,144,657 | 0.90% |
| 2022_03_01_desktop | 2,319,518 | 259,249,013 | 0.89% |