
CVE-2022-0778 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जो गैर-प्राइम कर्व पैरामीटर वाले क्राफ्टेड X.509 प्रमाणपत्र के माध्यम से OpenSSL के BN_mod_sqrt में अनंत लूप प्रदर्शित करता है, जिससे डिनायल-ऑफ-सर्विस हमले संभव होते हैं।
खोजी गई भेद्यता OpenSSL के BN_mod_sqrt() फ़ंक्शन में एक अनंत लूप ट्रिगर करती है जब यह एक अण्डाकार वक्र कुंजी का विश्लेषण करता है। इसका मतलब है कि एक दुर्भावनापूर्ण रूप से तैयार X.509 प्रमाणपत्र किसी भी अनपैच किए गए सर्वर को DoS कर सकता है।
भेद्यता का मूल संपीड़ित प्रारूप में बिंदुओं वाली EC कुंजियों के विश्लेषण में है: इस प्रकार की कुंजियों को पार्स करते समय, OpenSSL संपीड़ित बिंदु को विस्तारित करने का प्रयास करेगा, उस वक्र के प्राइम p मॉड्यूलो एक वर्गमूल की गणना करने का प्रयास करेगा जिस पर वक्र परिभाषित है। हालांकि, p की अभाज्यता कहीं भी जाँची नहीं जाती, यहाँ तक कि BN_mod_sqrt() में भी नहीं, जिसके लिए यह एक आवश्यकता है; इस प्रकार, कार्यान्वयन में एक बग के कारण अनंत लूप होगा क्योंकि p उम्मीद के अनुसार अभाज्य नहीं है।
मेरे ब्लॉग पर चीनी संस्करण देखें OpenSSL CVE-2022-0778 भेद्यता पुनरुत्पादन और अमान्य प्रमाणपत्र निर्माण
BN_mod_sqrt() के विरुद्ध परीक्षण प्रकाशित करने के लिएशर्त यह है कि gcc और OpenSSL का एक संवेदनशील संस्करण स्थापित हो।
BN_mod_sqrt() में बग के लिए: gcc -o my_bad_sqrt my_bad_sqrt.c -lcrypto के साथ संकलन करें, ./my_bad_sqrt चलाएँ और इसे हमेशा के लिए हैंग होते देखें! :D
प्रमाणपत्र के साथ: कमांड लाइन पर openssl x509 -in certs/cert.der.new -inform DER -text -noout चलाएँ; यह भी हैंग हो जाता है।
फ़ंक्शन BN_mod_sqrt() एक मॉड्यूलर वर्गमूल खोजने के लिए टोनेली-शैंक्स एल्गोरिथ्म को लागू करता है, अर्थात एक पूर्णांक a और एक अभाज्य संख्या p दिए जाने पर, एक मान r लौटाता है जैसे कि r^2 == a (mod p)।
भेद्यता को पैच करने वाले कमिट का विश्लेषण करने पर हम देखते हैं कि अपराधी वह लूप है जो सबसे छोटा सूचकांक i ढूंढता है जिसके लिए b^(2^i)==1 (mod p), जहाँ b को पहले एल्गोरिथ्म में परिभाषित किया गया है।
लूप को इस प्रकार बदला गया है
i = 1;
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
से
for (i = 1; i < e; i++) {
if (i == 1) {
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
} else {
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
if (BN_is_one(t))
break;
}
दूसरे मामले में, चर e द्वारा सीमित एक for लूप है; मूल कोड में हालांकि while लूप के अंदर केवल i==e मामले की जाँच है।
चूंकि ये लूप एक बड़े लूप के अंदर हैं जो प्रत्येक पुनरावृत्ति के लिए e का नया मान i के वर्तमान मान पर सेट करता है, हम निम्नलिखित हमला रणनीति का प्रयास करते हैं:
i=1 हो; इसके लिए, हमें चाहिए कि b^2=1 (mod p)।e=1 होगा, और यदि हम आंतरिक लूप में प्रवेश कर सकते हैं तो i==e जाँच हमेशा विफल रहेगी।t != 1 (mod p) रखने में सफल होते हैं, तो हम लूप में हमेशा के लिए रहेंगे।ध्यान दें कि पहले दो चरण वास्तव में एक "सामान्य" निष्पादन में हो सकते हैं, अर्थात एक अभाज्य p के साथ। हालांकि, यदि p संयुक्त है, तो हम तीसरे चरण को भी संतुष्ट कर सकते हैं!
टोनेली-शैंक्स एल्गोरिथ्म p - 1 = 2^e * q लिखकर काम करता है, एक विषम q के साथ। यह p मॉड्यूलो पूर्णांकों के गुणात्मक समूह का क्रम भी है, और मान e और q को निष्पादन के दौरान कई बार उपयोग किया जाएगा; हालांकि, यदि p अभाज्य नहीं है, तो गुणात्मक समूह का क्रम p-1 नहीं होगा, और यह हमें अनंत लूप में प्रवेश करने में मदद करेगा।
विशेष रूप से, b को b = a^q (mod p) के रूप में आरंभ किया जाता है, जिसका अर्थ है कि यदि p अभाज्य होता, तो b का क्रम 2 की कुछ घात होता, जिसे हम तब लूप का उपयोग करके ढूंढेंगे।
लेकिन यदि हम p = r * s सेट करते हैं, तो गुणात्मक समूह का क्रम (r-1)*(s-1) = r*s - r - s + 1 होता है, न कि r*s-1। एल्गोरिथ्म q मान का उपयोग एक ऐसा अवयव y प्राप्त करने के लिए करता है जिसका क्रम बिल्कुल 2^e है; हालांकि, जब p अभाज्य नहीं है, तो q मान का क्रम के लिए कोई विशेष अर्थ नहीं होगा, इसलिए अवयव y का क्रम p=r*s मॉड्यूलो दो की घात नहीं होगा।
चूंकि पहले बाहरी लूप के अंत में b को b = b*y^(e-i) (mod p) पर सेट किया जाता है, इसके दूसरे पुनरावृत्ति पर आंतरिक लूप एक मान i खोजने का प्रयास करेगा जिसके लिए b^(2^i) == 1 (mod p), लेकिन विफल हो जाएगा क्योंकि y का अब दो की घात होना गारंटी नहीं है।
शोषण में संख्याएँ बहुत सरल हैं: हम r=17, s=41 लेते हैं, जो p=r*s=697 देता है। इसका मतलब है कि e और q के परिकलित मान p-1 = 2^3 * 87 होंगे।
हम तब a=696 चुनते हैं, जिसका अर्थ है कि a == -1 (mod p) और आरंभ करने पर b == -1 (mod p) भी। यह e=1 सेट करके अगले बाहरी लूप के लिए चरण 1 को संतुष्ट करेगा।
फिर b को एक ऐसे अवयव पर सेट किया जाएगा जिसका क्रम 2 की घात नहीं है, और आंतरिक लूप एक i खोजने का प्रयास करते हुए फंस जाएगा जिसके लिए b^(2^i)==1 (mod p)।
ठीक है, अब चलिए एक खतरनाक प्रमाणपत्र तैयार करते हैं जिसमें संपीड़ित रूप में एन्कोडेड आधार बिंदु के साथ अमान्य स्पष्ट वक्र पैरामीटर हों।
इस अनुभाग की फ़ाइलें certs फ़ोल्डर में हैं।
पहले, हमें एक EC निजी कुंजी बनाने की आवश्यकता है। क्योंकि हम स्पष्ट वक्र पैरामीटर वाला एक लक्ष्य प्रमाणपत्र चाहते हैं, इसलिए कुंजी में भी स्पष्ट वक्र पैरामीटर होने चाहिए।
$ openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit -conv_form compressed
फिर, सुविधा के लिए, हमने एक स्व-हस्ताक्षरित प्रमाणपत्र बनाया और इसे DER प्रारूप में आउटपुट किया।
$ openssl req -new -x509 -key ec.key -out cert.der -outform DER -days 360 -subj "/CN=TEST/"
आइए प्रमाणपत्र जानकारी देखें। इसमें अपेक्षित स्पष्ट वक्र पैरामीटर हैं।
$ openssl x509 -in cert.der -text -noout -inform DER
...
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff
A:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fc
B:
5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
60:4b
Generator (compressed):
03:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
98:c2:96
Order:
00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
63:25:51
...
अब हमें इन पैरामीटरों के मानों को संशोधित करने की आवश्यकता है: Prime, A, B, Generator। वे नीचे दिए गए समीकरण को संतुष्ट करते हैं
जहाँ p Prime है, a पैरामीटर A है, और b पैरामीटर B है। p, a, b एक साथ एक वक्र निर्धारित करते हैं। एक बिंदु को अनकंप्रेस करने का अर्थ है संबंधित X निर्देशांक से Y निर्देशांक की गणना करना।
स्पष्ट रूप से, मॉड्यूलर वर्गमूल संक्रिया का उपयोग किया जाना चाहिए, जो BN_mod_sqrt() को कॉल करेगी।