लेखक: जिउजिउ
संपादित किया गया: 77
पृष्ठभूमि
4 सितंबर, 2026 को, प्रसिद्ध डिसेंट्रलाइज्ड लेंडिंग प्लेटफॉर्म Notional Finance पर हमला हुआ, जिसमें लगभग 1.73 मिलियन अमेरिकी डॉलर की हानि हुई। नीचे स्लो मिस्ट सुरक्षा टीम द्वारा इस हमले के बारे में विस्तृत विश्लेषण दिया गया है:
पूर्व ज्ञान
Notional Finance V1 में, fCash को एक निश्चित परिपक्वता तिथि वाले नकदी हिस्सेदारी के रूप में समझा जा सकता है। परिपक्वता पर, कैश रिसीवर राशि प्राप्त करता है और कैश पेयर राशि भुगतान करता है। प्रोटोकॉल दोनों पोजीशन को पोर्टफोलियो में रिकॉर्ड करता है और फिर स्वतंत्र प्रतिभूति जांच के माध्यम से एक खाते की पोजीशन बनाने की स्वास्थ्य क्षमता का आकलन करता है।
ERC1155Trade का safeTransferFrom यहाँ संपत्ति लेखांकन का कार्य करता है। जब आगमित संपत्ति प्रकार cash receiver से संबंधित होता है, तो कॉन्ट्रैक्ट Portfolios.mintfCashPair को कॉल करता है और साथ ही payer के लिए एक दायित्व और receiver के लिए एक समान ऋण दर्ज करता है। यह कॉल ERC1155 ट्रांसफर की तरह दिखता है, लेकिन परिणाम में fCash जोड़ी का निर्माण होता है।
पोर्टफोलियो में नया पोजीशन लिखने के बाद, मुक्त प्रतिभूति की गणना और जांच की आवश्यकता होती है। RiskFramework कॉन्ट्रैक्ट पहले payer के fCash दायित्व को एक संकेतित int256 में समेटता है, फिर प्रत्येक क्रिप्टोकरेंसी के शेष को ETH में बदलकर समेटता है। ऋणात्मक संख्या भुगतान करने के लिए नकदी को दर्शाती है, जबकि धनात्मक संख्या खाते में उपलब्ध नकदी या ऋण को दर्शाती है। यह गणना यह निर्धारित करती है कि लेनदेन की अनुमति दी जा सकती है या नहीं।
पोजिशन के समाप्त होने के बाद, पोर्टफोलियो Escrow के portfolioSettleCash फ़ंक्शन को कॉल करेगा, जिससे समाप्त fCash नकद शेष में बदल जाएगा। फिर खाता Escrow से संबंधित DAI या USDC संपत्ति निकालने के लिए निकासी फ़ंक्शन का उपयोग करेगा।
मूल कारण
इस हमले की केंद्रीय वैधता इस बात में है कि Escrow कार्यान्वयन कॉन्ट्रैक्ट द्वारा उपयोग किया जाने वाला ExchangeRate._convertToETH, जिसमें एक कोड है जो साइन्ड पूर्णांक के निरपेक्ष मान को सीधे uint128 में बदल देता है।

आक्रमक ने दो समान पेयर के दायित्व स्थितियाँ बनाईं, जिनकी राशि क्रमशः 1 और uint128.max है। दोनों दायित्वों को जोड़ने पर ठीक 2^128 प्राप्त होता है। चूंकि जोखिम गणना में पेयर द्वारा भुगतान को ऋणात्मक संख्या के रूप में दर्ज किया जाता है, इसलिए Escrow के convertBalancesToETH में पास किया गया महत्वपूर्ण पैरामीटर -2^128 है।
लेकिन इस कॉन्ट्रैक्ट के द्वारा उपयोग किया जा रहा Solidity संस्करण 0.6.x है, इसलिए इसका निरपेक्ष मान 2^128 को uint128 में बदलने पर ओवरफ्लो हो जाता है और 0 हो जाता है, जिससे यह दायित्व ETH मूल्यांकन परिणाम में शामिल नहीं होता है।
अंतिम रूप से, mintfCashPair फ़ंक्शन में केवल भुगतानकर्ता का मुक्त प्रतिभूति शून्य या उससे अधिक होने की आवश्यकता होती है, इसलिए परिणाम शून्य पर ओवरफ्लो होने के बाद भी यह जांच पास हो जाती है, और एक ऐसा खाता जिसमें देयता को कवर करने के लिए पर्याप्त संपत्ति नहीं है, फिर भी पोजीशन बना सकता है।
आक्रमण चरणों का विश्लेषण
1. प्री-ट्रेड (0xe1589a19…d60a) में, हमलावर ने पहले कई सहायक स्मार्ट कॉन्ट्रैक्ट बनाए और सहायक कॉन्ट्रैक्ट के लिए ERC1155Trade कॉन्ट्रैक्ट के setApprovalForAll फ़ंक्शन को कॉल किया; इसके अलावा, उन्होंने दो CashMarket की एक्सपायरी तिथियों की पहले से जांच की और आगे के कार्रवाई में पास किए जाने वाले तीन AssetId पैरामीटर मानों की गणना की।

2. इसके तुरंत बाद, हमलावर ने ERC1155Trade कॉन्ट्रैक्ट के safeTransferFrom फ़ंक्शन को कॉल करके 1 की मात्रा का fCash जोड़ा, जिसके लिए बॉन्ड संपत्ति का cashGroupId 2 है और परिपक्वता टाइमस्टैम्प 1788480000 (4 सितंबर, 2026, 8:00 AM) है। इसमें _upsertAsset आंतरिक फ़ंक्शन को from पते (हमलावर कॉन्ट्रैक्ट) और to पते (स्वीकार करने वाला कॉन्ट्रैक्ट 1) के लिए अपने दायित्व और अपेक्षित आय को अपडेट करने के लिए कॉल किया जाता है।

जब जोड़ी का निर्माण पूरा हो जाता है, तो Portfolios तुरंत हमला करने वाले स्मार्ट कॉन्ट्रैक्ट की मुक्त प्रतिभूति की जांच करता है, और चूंकि पहले निर्माण की मात्रा बहुत कम होती है, इसलिए वर्तमान बाजार दर और सटीकता के अनुसार ETH में परिवर्तित करने पर यह शून्य में गोल हो जाती है, इसलिए पहली जांच सफल हो जाती है।
3. आक्रमणकारी ने बाद में ERC1155Trade कॉन्ट्रैक्ट के safeTransferFrom फ़ंक्शन को फिर से कॉल किया और uint128.max की मात्रा में fCash जोड़ी जारी की, इस बार संबंधित बॉन्ड संपत्ति का cashGroupId 2 था, लेकिन परिपक्वता टाइमस्टैम्प 1796256000 (3 दिसंबर, 2026, 8:00 AM) था।
यहाँ एक विवरण है, हमलावर ने दो अलग-अलग पतों को अलग-अलग परिपक्वता तिथि वाले बॉन्ड संपत्ति जारी कीं। इसका कारण यह है कि from पते पर दायित्व को अपडेट करते समय, यदि बॉन्ड संपत्ति समान है, तो इसे सीधे जोड़ दिया जाता है, जिससे जोड़ के समय ओवरफ्लो के कारण revert हो जाता है (कॉन्ट्रैक्ट safeMath पुस्तकालय का उपयोग करता है)।

4. दूसरे मिंटिंग जोड़ी के लिए from पते (हमला कॉन्ट्रैक्ट) और to पते (स्वीकार कॉन्ट्रैक्ट 2) के बाद दायित्व और अपेक्षित आय को अपडेट करने के बाद, mintfCashPair फ़ंक्शन freeCollateral फ़ंक्शन को कॉल करता है ताकि from पते की शुद्ध प्रतिभूति स्थिति की गणना की जा सके और इसकी जांच की जा सके कि यह 0 के बराबर या उससे अधिक है।

इसमें पहले RiskFramework कॉन्ट्रैक्ट में getRequirement फ़ंक्शन में from पते के दो बार मिंट किए गए दायित्वों को जोड़ा जाएगा:

अंतिम परिणाम -2^128 है, जिसके बाद Escrow कॉन्ट्रैक्ट का convertBalancesToETH फ़ंक्शन इस मान को ETH में बदलने के लिए कॉल किया जाता है, और convertBalancesToETH फ़ंक्शन गणना के लिए ExchangeRate लाइब्रेरी का _convertToETH फ़ंक्शन कॉल करता है:


_convertToETH फ़ंक्शन में जाने पर, यह पाया जाता है कि यह पहले balance का निरपेक्ष मान लेता है और फिर uint128 का उपयोग करके 256-बिट को 128-बिट में बदल देता है, जबकि ऊपर from की कुल देनदारी की मात्रा -2^128 है; निरपेक्ष मान लेने के बाद 2^128 को uint128() में स्पष्ट रूप से परिवर्तित करने पर सीमा से अधिक हो जाने के कारण यह 0 पर ओवरफ्लो हो जाता है। इसका अर्थ है कि प्रोटोकॉल from पते की स्वतंत्र प्रतिभूति स्थिति को 0 मानता है, जो स्वस्थ मानी जाती है, और इस प्रकार mintfCashPair फ़ंक्शन की अंतिम जांच से गुजर जाता है।

5. इसके बाद, कॉन्ट्रैक्ट 2 द्वारा प्राप्त कॉन्ट्रैक्ट की स्थिति को दो अन्य सहायक कॉन्ट्रैक्ट में विभाजित कर दिया गया, जिनमें से प्रत्येक safeTransferFrom मिंटफ़ैक्शपेयर फ़ंक्शन में प्रवेश करेगा। हालाँकि, चूंकि इस समय पेयर कॉन्ट्रैक्ट 2 है, जो दूसरी बार मिंट करते समय प्राप्त हुआ था, और इसके पास पिछले चरण में प्राप्त विशाल अपेक्षित आय स्थिति है, जिसे जोखिम की गणना धनात्मक हिस्सेदारी के रूप में मानती है, इसलिए यह प्रतिभूति जाँच से गुजर जाती है, और इस प्रकार दोनों सहायक कॉन्ट्रैक्ट को अंततः नकदी में बदले जा सकने वाले receiver fCash प्राप्त होते हैं।

6. आक्रमक ने बाद में एक दूसरी औपचारिक लाभ लेनदेन शुरू की, जिसमें पिछली लेनदेन के दोनों होल्डिंग पोजीशन के सहायक स्मार्ट कॉन्ट्रैक्ट्स के संबंधित हकदारी का निपटान किया गया, संबंधित संपत्ति के नकद शेष में वृद्धि की गई, और फिर Escrow कॉन्ट्रैक्ट के withdraw फ़ंक्शन को कॉल करके अपनी संपत्ति निकाली गई।
Summary
इस हमले की मुख्य बात यह है कि हमलावर ने पहले दो अलग-अलग पोजीशन का उपयोग करके payer की कुल देनदारी को 2^128 तक पहुँचाया, फिर Escrow को इस देनदारी को ETH में बदलने के लिए भेजा। टाइप कन्वर्जन में ओवरफ्लो दोष का उपयोग करके परिणाम को शून्य में काट दिया गया, जिससे जोखिम जाँच से गुजर गया।
मॉन सिक्योरिटी टीम सुझाव देती है कि प्रोजेक्ट टीम को टाइप कन्वर्जन से पहले रेंज चेक करना अनिवार्य है; type(uint128).max से अधिक परिणाम को सीधे अस्वीकार कर देना चाहिए। इसके साथ ही, सभी साइन्ड अमाउंट, प्रिसिजन स्केलिंग और एसेट फेस वैल्यू की सीमाओं के लिए एक्सट्रीम वैल्यू टेस्टिंग शामिल करें, विशेष रूप से uint128.max, uint128.max + 1 और नेगेटिव एब्सोल्यूट जैसे इनपुट्स को कवर करें। SafeCast लाइब्रेरी का उपयोग या संदर्भ लेने के लिए OpenZeppelin का उपयोग करें।
