आजकल शायद ही कोई ऐसा व्यक्ति होगा जिसने गहरी निराशा में "पासवर्ड पुनर्प्राप्त करें" बटन पर क्लिक न किया हो। भले ही ऐसा लगे कि पासवर्ड बिना किसी संदेह के सही था, इसे पुनर्प्राप्त करने का अगला चरण ईमेल से एक लिंक पर जाकर और नया पासवर्ड दर्ज करके आसानी से हो जाता है (चलो किसी को बेवकूफ़ न बनाएँ; यह बिल्कुल भी नया नहीं है क्योंकि आपने पहले चरण में तीन बार टाइप किया है और फिर अप्रिय बटन दबा दिया है)।
हालाँकि, ईमेल लिंक के पीछे का तर्क कुछ ऐसा है जिसकी गहन जांच की जानी चाहिए क्योंकि इसे असुरक्षित छोड़ने से उपयोगकर्ता खातों तक अनधिकृत पहुँच के बारे में कमज़ोरियों की बाढ़ आ जाती है। दुर्भाग्य से, यहाँ UUID-आधारित पुनर्प्राप्ति URL संरचना का एक उदाहरण दिया गया है जिसका सामना कई लोगों ने किया होगा, जो फिर भी सुरक्षा दिशानिर्देशों का पालन नहीं करता है:
https://.../recover/d17ff6da-f5bf-11ee-9ce2-35a784c01695
यदि इस तरह के लिंक का उपयोग किया जाता है, तो इसका आम तौर पर मतलब है कि कोई भी आपका पासवर्ड प्राप्त कर सकता है, और यह इतना ही सरल है। इस लेख का उद्देश्य UUID निर्माण विधियों में गहराई से जाना और उनके अनुप्रयोग के लिए असुरक्षित दृष्टिकोणों का चयन करना है।
UUID एक 128-बिट लेबल है जिसका उपयोग आमतौर पर छद्म यादृच्छिक पहचानकर्ता बनाने में किया जाता है जिसमें दो मूल्यवान विशेषताएँ होती हैं: यह पर्याप्त जटिल और पर्याप्त अद्वितीय होता है। ज़्यादातर, ये ID के लिए मुख्य आवश्यकताएँ होती हैं जो बैकएंड से बाहर निकलती हैं और उपयोगकर्ता को फ़्रंटएंड में स्पष्ट रूप से दिखाई देती हैं या आम तौर पर API पर भेजी जाती हैं, जिसमें अवलोकन की क्षमता होती है। यह id = 123 (जटिलता) की तुलना में अनुमान लगाना या बलपूर्वक उपयोग करना कठिन बनाता है और जब उत्पन्न id को पहले से उपयोग की गई id में डुप्लिकेट किया जाता है, तो टकराव को रोकता है, उदाहरण के लिए, 0 से 1000 तक की एक यादृच्छिक संख्या (विशिष्टता)।
"पर्याप्त" भाग वास्तव में, सबसे पहले, यूनिवर्सली यूनिक आईडेंटिफायर के कुछ संस्करणों से आते हैं, जो इसे दोहराव के लिए छोटी संभावनाओं के लिए खुला छोड़ देते हैं, जिसे, हालांकि, अतिरिक्त तुलना तर्क द्वारा आसानी से कम किया जा सकता है और इसकी घटना के लिए मुश्किल से नियंत्रित स्थितियों के कारण कोई खतरा पैदा नहीं करता है। और दूसरी बात, लेख में विभिन्न UUID संस्करणों की जटिलता का वर्णन किया गया है, सामान्य तौर पर इसे आगे के कोने के मामलों को छोड़कर काफी अच्छा माना जाता है।
डेटाबेस तालिकाओं में प्राथमिक कुंजियाँ जटिल और अद्वितीय होने के उन्हीं सिद्धांतों पर निर्भर करती हैं, जैसे UUID करता है। कई प्रोग्रामिंग भाषाओं और डेटाबेस प्रबंधन प्रणालियों में इसके निर्माण के लिए अंतर्निहित विधियों को व्यापक रूप से अपनाए जाने के साथ, UUID अक्सर संग्रहीत डेटा प्रविष्टियों की पहचान करने और सामान्य रूप से तालिकाओं और सामान्यीकरण द्वारा विभाजित उप-तालिकाओं को जोड़ने के लिए एक फ़ील्ड के रूप में पहली पसंद के रूप में आता है। कुछ क्रियाओं के जवाब में API पर डेटाबेस से आने वाले उपयोगकर्ता आईडी भेजना भी अतिरिक्त अस्थायी आईडी निर्माण के बिना डेटा प्रवाह को एकीकृत करने की प्रक्रिया को सरल बनाने और उन्हें उत्पादन डेटा संग्रहण में लोगों से जोड़ने के लिए सामान्य अभ्यास है।
पासवर्ड रीसेट उदाहरणों के संदर्भ में, आर्किटेक्चर में इस तरह के ऑपरेशन के लिए जिम्मेदार एक टेबल शामिल होने की अधिक संभावना है जो हर बार जब कोई उपयोगकर्ता बटन पर क्लिक करता है तो जेनरेट किए गए UUID के साथ डेटा की पंक्तियाँ सम्मिलित करता है। यह उपयोगकर्ता के साथ उनके user_id द्वारा जुड़े पते पर एक ईमेल भेजकर पुनर्प्राप्ति प्रक्रिया शुरू करता है और रीसेट लिंक खुलने के बाद उनके पास मौजूद पहचानकर्ता के आधार पर यह जाँच करता है कि किस उपयोगकर्ता के लिए पासवर्ड रीसेट करना है। हालाँकि, उपयोगकर्ताओं को दिखाई देने वाले ऐसे पहचानकर्ताओं के लिए सुरक्षा दिशा-निर्देश हैं, और UUID के कुछ कार्यान्वयन अलग-अलग डिग्री की सफलता के साथ उन्हें पूरा करते हैं।
UUID जनरेशन का संस्करण 1 अपने 128 बिट्स को डिवाइस जनरेटिंग आइडेंटिफ़ायर के 48-बिट MAC एड्रेस, 60-बिट टाइमस्टैम्प, 14-बिट स्टोर किए गए मूल्य को बढ़ाने के लिए और 6 को वर्जनिंग के लिए उपयोग करने में विभाजित करता है। इस प्रकार, कोड लॉजिक में नियमों से विशिष्टता की गारंटी हार्डवेयर निर्माताओं को हस्तांतरित की जाती है, जिन्हें उत्पादन में प्रत्येक नई मशीन के लिए सही तरीके से मान निर्दिष्ट करना होता है। उपयोगी परिवर्तनीय पेलोड का प्रतिनिधित्व करने के लिए केवल 60+14 बिट्स छोड़ना पहचानकर्ता की अखंडता को खराब करता है, खासकर इसके पीछे ऐसे पारदर्शी तर्क के साथ। आइए UUID v1 की परिणामी संख्या के अनुक्रम पर एक नज़र डालें:
from uuid import uuid1 for _ in range(8): print(uuid1())
d17ff6da-f5bf-11ee-9ce2-35a784c01695 d17ff6db-f5bf-11ee-9ce2-35a784c01695 d17ff6dc-f5bf-11ee-9ce2-35a784c01695 d17ff6dd-f5bf-11ee-9ce2-35a784c01695 d17ff6de-f5bf-11ee-9ce2-35a784c01695 d17ff6df-f5bf-11ee-9ce2-35a784c01695 d17ff6e0-f5bf-11ee-9ce2-35a784c01695 d17ff6e1-f5bf-11ee-9ce2-35a784c01695
जैसा कि देखा जा सकता है, "-f5bf-11ee-9ce2-35a784c01695" भाग हमेशा एक जैसा रहता है। परिवर्तनीय भाग केवल अनुक्रम 3514824410 - 3514824417 का 16-बिट हेक्साडेसिमल प्रतिनिधित्व है। यह एक सतही उदाहरण है क्योंकि उत्पादन मूल्य आमतौर पर बीच में समय में अधिक महत्वपूर्ण अंतराल के साथ उत्पन्न होते हैं, इसलिए टाइमस्टैम्प से संबंधित भाग भी बदल जाता है। 60-बिट टाइमस्टैम्प भाग का यह भी अर्थ है कि पहचानकर्ता का अधिक महत्वपूर्ण भाग आईडी के बड़े नमूने पर दृष्टिगत रूप से बदल जाता है। मुख्य बिंदु वही रहता है: UUIDv1 का आसानी से अनुमान लगाया जा सकता है, हालाँकि यह शुरू में यादृच्छिक दिखता है।
8 आईडी की दी गई सूची में से केवल पहला और अंतिम मान लें। चूंकि पहचानकर्ता सख्ती से उत्पन्न होते हैं, परिणामस्वरूप, यह स्पष्ट है कि दिए गए दो के बीच केवल 6 आईडी उत्पन्न हुए हैं (हेक्साडेसिमल परिवर्तनीय भागों को घटाकर), और उनके मान भी निश्चित रूप से पाए जा सकते हैं। इस तरह के तर्क का एक्सट्रपलेशन तथाकथित सैंडविच हमले के पीछे अंतर्निहित हिस्सा है जिसका लक्ष्य यूयूआईडी को इन दो सीमा मूल्यों को जानने से रोकना है। हमले का प्रवाह सीधा है: उपयोगकर्ता लक्ष्य यूयूआईडी पीढ़ी से पहले यूयूआईडी ए उत्पन्न करता है और उसके ठीक बाद यूयूआईडी बी। यह मानते हुए कि एक स्थिर 48-बिट मैक भाग वाला एक ही डिवाइस तीनों पीढ़ियों के लिए जिम्मेदार है, यह उपयोगकर्ता को ए और बी के बीच संभावित आईडी के अनुक्रम के साथ सेट करता है, जहां लक्ष्य यूयूआईडी स्थित है।
पहले वर्णित पासवर्ड रिकवरी एंडपॉइंट के साथ API अनुरोधों में, इसका अर्थ है सैकड़ों या हज़ारों अनुरोधों को परिणामी UUIDs के साथ भेजना जब तक कि मौजूदा URL बताने वाला कोई जवाब न मिल जाए। पासवर्ड रीसेट के साथ, यह एक सेटअप की ओर ले जाता है जहाँ उपयोगकर्ता अपने द्वारा नियंत्रित दो खातों पर रिकवरी लिंक उत्पन्न कर सकता है ताकि लक्ष्य खाते पर रिकवरी बटन को दबाया जा सके जिस तक उनकी कोई पहुँच नहीं है लेकिन केवल ईमेल/लॉगिन जानते हैं। रिकवरी UUIDs A और B वाले नियंत्रित खातों के लिए पत्र तब ज्ञात होते हैं, और लक्ष्य खाते के लिए पासवर्ड पुनर्प्राप्त करने के लिए लक्ष्य लिंक को वास्तविक रीसेट ईमेल तक पहुँच के बिना ब्रूट-फोर्स किया जा सकता है।
भेद्यता उपयोगकर्ता प्रमाणीकरण के लिए केवल UUIDv1 पर निर्भर रहने की अवधारणा से उत्पन्न होती है। पासवर्ड रीसेट करने की पहुँच प्रदान करने वाले रिकवरी लिंक को भेजकर, यह माना जाता है कि लिंक का अनुसरण करके, उपयोगकर्ता को उस व्यक्ति के रूप में प्रमाणित किया जाता है जिसे लिंक प्राप्त करना था। यह वह हिस्सा है जहाँ प्रमाणीकरण नियम विफल हो जाता है क्योंकि UUIDv1 सीधे क्रूर बल के संपर्क में आता है, ठीक उसी तरह जैसे कि किसी के दरवाजे को उसके दोनों पड़ोसी दरवाजों की चाबियों की तरह दिखने के बारे में जानकर खोला जा सकता है।
UUID का पहला संस्करण मुख्य रूप से विरासत माना जाता है, आंशिक रूप से इसलिए क्योंकि जनरेशन लॉजिक केवल पहचानकर्ता आकार के एक छोटे हिस्से को यादृच्छिक मान के रूप में उपयोग करता है। अन्य संस्करण, जैसे v4, संस्करण के लिए यथासंभव कम स्थान रखकर और यादृच्छिक पेलोड के लिए 122 बिट्स तक छोड़कर इस समस्या को हल करने का प्रयास करते हैं। सामान्य तौर पर, यह कुल संभावित विविधताओं को 2^122
तक ले आता है, जिसे अभी के लिए पहचानकर्ता विशिष्टता आवश्यकता के संबंध में "पर्याप्त" भाग को संतुष्ट करने और इस प्रकार सुरक्षा मानकों को पूरा करने के लिए माना जाता है। यदि जनरेशन कार्यान्वयन किसी तरह यादृच्छिक भाग के लिए छोड़े गए बिट्स को महत्वपूर्ण रूप से कम कर देता है, तो क्रूर-बल भेद्यता के लिए रास्ता खुल सकता है। लेकिन बिना किसी उत्पादन उपकरण या लाइब्रेरी के, क्या ऐसा होना चाहिए?
आइए क्रिप्टोग्राफी में थोड़ा सा शामिल हों और UUID जेनरेशन के जावास्क्रिप्ट के सामान्य कार्यान्वयन पर करीब से नज़र डालें। यहाँ छद्म-यादृच्छिक संख्या जेनरेशन के लिए math.random
मॉड्यूल पर निर्भर randomUUID()
फ़ंक्शन है:
Math.floor(Math.random()*0x10);
और यादृच्छिक फ़ंक्शन स्वयं, संक्षेप में यह इस लेख के विषय के लिए रुचि का हिस्सा है:
hi = 36969 * (hi & 0xFFFF) + (hi >> 16); lo = 18273 * (lo & 0xFFFF) + (lo >> 16); return ((hi << 16) + (lo & 0xFFFF)) / Math.pow(2, 32);
छद्म यादृच्छिक पीढ़ी को यादृच्छिक पर्याप्त संख्याओं के अनुक्रम बनाने के लिए इसके ऊपर गणितीय संचालन करने के लिए आधार के रूप में बीज मान की आवश्यकता होती है। ऐसे फ़ंक्शन पूरी तरह से इस पर आधारित होते हैं, जिसका अर्थ है कि यदि उन्हें पहले की तरह ही बीज के साथ पुनः आरंभ किया जाता है, तो आउटपुट अनुक्रम मेल खाने वाला है। विचाराधीन जावास्क्रिप्ट फ़ंक्शन में बीज मान में hi और lo चर शामिल हैं, जिनमें से प्रत्येक 32-बिट अहस्ताक्षरित पूर्णांक (0 से 4294967295 दशमलव) है। क्रिप्टोग्राफ़िक उद्देश्यों के लिए दोनों के संयोजन की आवश्यकता होती है, जिससे दो प्रारंभिक मानों को उनके गुणकों को जानकर निश्चित रूप से उलटना लगभग असंभव हो जाता है, क्योंकि यह बड़ी संख्याओं के साथ पूर्णांक कारकीकरण की जटिलता पर निर्भर करता है।
दो 32-बिट पूर्णांक एक साथ UUIDs का उत्पादन करने वाले आरंभिक फ़ंक्शन के पीछे hi और lo चर का अनुमान लगाने के लिए 2^64
संभावित मामले लाते हैं। यदि hi और lo मान किसी तरह से ज्ञात हैं, तो जनरेशन फ़ंक्शन को डुप्लिकेट करने और यह जानने के लिए कोई प्रयास नहीं करना पड़ता है कि यह सभी मान उत्पन्न करता है और भविष्य में बीज मान एक्सपोज़र के कारण उत्पन्न करेगा। हालाँकि, सुरक्षा मानकों में 64 बिट्स को एक मापनीय समय अवधि में ब्रूट-फोर्स के लिए असहिष्णु माना जा सकता है ताकि यह समझ में आए। हमेशा की तरह, समस्या विशिष्ट कार्यान्वयन से आती है। Math.random()
hi और lo में से प्रत्येक से 32-बिट परिणामों में विभिन्न 16 बिट्स लेता है; हालाँकि, इसके ऊपर randomUUID()
.floor()
ऑपरेशन के कारण एक बार फिर से मान को बदल देता है, और अब अचानक एकमात्र सार्थक हिस्सा विशेष रूप से hi से आता है। यह किसी भी तरह से पीढ़ी को प्रभावित नहीं करता है, लेकिन क्रिप्टोग्राफी दृष्टिकोण को अलग कर देता है क्योंकि यह पूरे पीढ़ी फ़ंक्शन बीज के लिए केवल 2^32
संभावित संयोजनों को छोड़ देता है (हाय और लो दोनों को ब्रूट-फोर्स करने की कोई आवश्यकता नहीं है क्योंकि लो को किसी भी मूल्य पर सेट किया जा सकता है और यह आउटपुट को प्रभावित नहीं करता है)।
ब्रूट-फोर्स फ्लो में एक एकल आईडी प्राप्त करना और संभावित उच्च मानों का परीक्षण करना शामिल है जो इसे उत्पन्न कर सकते थे। कुछ अनुकूलन और औसत लैपटॉप हार्डवेयर के साथ, इसमें बस कुछ मिनट लग सकते हैं और सैंडविच हमले की तरह सर्वर को बहुत सारे अनुरोध भेजने की आवश्यकता नहीं होती है, बल्कि सभी ऑपरेशन ऑफ़लाइन किए जाते हैं। इस तरह के दृष्टिकोण का परिणाम पासवर्ड रिकवरी उदाहरण में सभी बनाए गए और भविष्य के रीसेट लिंक प्राप्त करने के लिए बैकएंड में उपयोग किए जाने वाले जनरेशन फ़ंक्शन स्थिति की प्रतिकृति का कारण बनता है। भेद्यता को उभरने से रोकने के लिए कदम सीधे हैं और क्रिप्टोग्राफ़िक रूप से सुरक्षित फ़ंक्शन के उपयोग के लिए चिल्लाते हैं, जैसे कि crypto.randomUUID()
।
UUID एक बेहतरीन अवधारणा है और यह कई अनुप्रयोग क्षेत्रों में डेटा इंजीनियरों के जीवन को बहुत आसान बनाती है। हालाँकि, इसे प्रमाणीकरण के संबंध में कभी भी इस्तेमाल नहीं किया जाना चाहिए, क्योंकि इस लेख में, इसकी निर्माण तकनीकों के कुछ मामलों में खामियाँ सामने आई हैं। यह स्पष्ट रूप से सभी UUID के असुरक्षित होने के विचार का अनुवाद नहीं करता है। हालाँकि, मूल दृष्टिकोण लोगों को सुरक्षा के लिए उनका उपयोग न करने के लिए राजी करना है, जो कि दस्तावेज़ीकरण में जटिल सीमाएँ निर्धारित करने की तुलना में अधिक कुशल और, अच्छी तरह से सुरक्षित है कि किसका उपयोग किया जाए या इस तरह के उद्देश्य के लिए उन्हें कैसे उत्पन्न न किया जाए।