
Exim Use-After-Free (CVE-2020-28018) के लिए एक्सप्लॉइट, जो मेमोरी भ्रष्टाचार प्रिमिटिव के माध्यम से रिमोट कोड निष्पादन प्राप्त करता है, जिसमें मनमाना रीड/राइट और हीप लीक शामिल हैं, साथ ही वैकल्पिक लोकल प्रिविलेज एस्केलेशन चेन भी।
tls-openssl.c में एक Use-after-free (UAF) भेद्यता मौजूद है जो दूरस्थ अनधिकृत हमलावरों को आंतरिक मेमोरी डेटा को दूषित करने की अनुमति देती है, इस प्रकार अंततः रिमोट कोड एक्सेक्यूशन प्राप्त होता है।
प्रिमिटिव्स:
इन सभी प्रिमिटिव्स को एक साथ श्रृंखलाबद्ध करके उपयोग करने पर सभी उपलब्ध एक्सप्लॉइट मिटिगेशन्स को पूरी तरह से बायपास करना संभव है, अंततः exim यूज़र के रूप में रिमोट कोड एक्सेक्यूशन तक पहुँचते हुए।
यह भेद्यता भेद्यताओं की एक विशाल सूची के बीच जारी की गई है, आधिकारिक Qualys रिपोर्ट RCE प्राप्त होने के बाद Local Privilege Escalation (LPE) करने के लिए Use-After-Free को CVE-2020-28008 के साथ श्रृंखलाबद्ध करती है।
exim को निम्नलिखित तरीके से कॉन्फ़िगर / संकलित किया जाना चाहिए:
X_PIPE_CONNECT अक्षम हैआप checker.py स्क्रिप्ट का उपयोग कर सकते हैं यह जाँचने के लिए कि क्या कोई दूरस्थ सर्वर भेद्य संस्करण पर है और उसके पास शोषण के लिए आवश्यक कुछ आवश्यकताएँ हैं या नहीं।
[!] checker.py भेद्यता को ट्रिगर नहीं करती, केवल भेद्य संस्करण की जाँच करती है, जाँचती है कि PIPELINING और TLS सक्षम हैं या नहीं। इसका मतलब है कि यह चेकर पैच की जाँच नहीं करता, जिसका अर्थ है कि यह गलत सकारात्मक परिणाम उत्पन्न कर सकता है।
जैसा कि हम पहले से जानते हैं, भेद्यता tls-openssl.c में स्थित है।
/*************************************************
* Write bytes down TLS channel *
*************************************************/
/*
Arguments:
ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
more further data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
Used by both server-side and client-side TLS.
*/
int
tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
int outbytes, error, left;
SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
static gstring * corked = NULL;
DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
buff, (unsigned long)len, more ? ", more" : "");
/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
"more" is notified. This hack is only ok if small amounts are involved AND only
one stream does it, in one context (i.e. no store reset). Currently it is used
for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
a store reset there. */
if (!ct_ctx && (more || corked))
{
#ifdef EXPERIMENTAL_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
#endif
corked = string_catn(corked, buff, len);
#ifdef EXPERIMENTAL_PIPE_CONNECT
store_pool = save_pool;
#endif
if (more)
return len;
buff = CUS corked->s;
len = corked->ptr;
corked = NULL;
}
for (left = len; left > 0;)
{
DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
switch (error)
{
case SSL_ERROR_SSL:
ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
return -1;
case SSL_ERROR_NONE:
left -= outbytes;
buff += outbytes;
break;
case SSL_ERROR_ZERO_RETURN:
log_write(0, LOG_MAIN, "SSL channel closed on write");
return -1;
case SSL_ERROR_SYSCALL:
log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
sender_fullhost ? sender_fullhost : US"<unknown>",
strerror(errno));
return -1;
default:
log_write(0, LOG_MAIN, "SSL_write error %d", error);
return -1;
}
}
return len;
}
smtp_setup_msg() मुख्य फ़ंक्शन है जो क्लाइंट से संदेश पढ़ने का कार्य करता है।
विशिष्ट परिस्थितियों में, smtp_reset() को कॉल किया जाता है, जो सभी बफ़र्स और मानों की सफाई करता है।
यह निम्नलिखित स्थितियों में हो सकता है:
HELO/EHLO प्राप्त होता हैSTARTTLS प्राप्त होता हैRSET प्राप्त होता हैsmtp_setup_msg() की शुरुआत मेंsmtp_reset() के अंत में, store_reset() के लिए एक कॉल की जाती है।
store_reset एक मैक्रो है जो store_reset_3() फ़ंक्शन को रैप करता है।
स्टोर फ़ंक्शन केवल ऐसे फ़ंक्शन हैं जो डायनामिक मेमोरी का प्रबंधन करते हैं।
Exim malloc से प्राप्त ब्लॉक्स पर पूल आवंटक (pool allocator) का उपयोग करता है।
एक दिलचस्प कार्यक्षमता भी है जो एक ग्रोएबल स्ट्रिंग (growable string) कार्यान्वयन है।
gstring स्ट्रक्चर:
typedef struct gstring {
int size; /* Current capacity of string memory */
int ptr; /* Offset at which to append further chars */
uschar * s; /* The string memory */
} gstring;
नई स्ट्रिंग को जोड़ने के लिए अधिक स्थान की आवश्यकता होने पर, यह gstring_grow() को कॉल करता है।
वह फ़ंक्शन पहले store_extend_3() को कॉल करने का प्रयास करता है, वह फ़ंक्शन उसी पूल ब्लॉक के भीतर मेमोरी को बढ़ाने का प्रयास करता है।
यह तब उपयोगी हो सकता है जब इनपुट की लंबाई ज्ञात न हो, लेकिन यदि इसके बाद अधिक मेमोरी आवंटित की गई थी तो हम इसे बढ़ा नहीं पाएंगे।
फिर gstring_grow() store_newblock_3() को कॉल करता है जो केवल नई मेमोरी लौटाता है और पिछली मेमोरी में पहले से मौजूद बाइट्स को नई मेमोरी में कॉपी करता है।
फिर g->s पॉइंटर को gstring_catn() से पुनर्स्थापित किया जाता है।
फ़ंक्शन tls_write() में, हम देख सकते हैं कि more नामक एक BOOL है।
यह इंगित करता है कि डेटा को उपयोगकर्ता को वापस लौटाने से पहले स्ट्रिंग बफ़र में और अधिक सामग्री कॉपी की जानी है या नहीं।
यदि ऐसा है, तो पॉइंटर NULL नहीं किया जाता।
यदि नहीं, तो स्ट्रिंग बफ़र में निहित डेटा उपयोगकर्ता को लौटा दिया जाता है।
यह कार्यक्षमता Use-After-Free को ट्रिगर करने के कुछ दिलचस्प तरीके खोलती है।
पहले, gstring स्ट्रक्चर का पॉइंटर एक स्टैटिक वेरिएबल में संग्रहीत होता है, इसका मतलब है कि भविष्य में tls_write() के कॉल पर हम इसका उपयोग कर पाएंगे।
हम बफ़र को मुक्त (free) करके फिर उसका उपयोग कैसे कर सकते हैं?
हमें smtp_setup_msg() को smtp_reset() कॉल करवाना होगा जब हमारा एक बफ़र अभी भी server_corked पर हो (NULL नहीं किया गया)।
रीसेट के बाद, यदि हम किसी तरह tls_write() को कॉल करते हैं, तो पॉइंटर अभी भी वहाँ होगा, इस प्रकार मेमोरी मुक्त होने के बाद हमें इसका उपयोग करने की अनुमति मिलती है।
smtp_reset() POOL_MAIN की सभी मेमोरी मुक्त करता है, जिसमें हमारा बफ़र समाहित है।
Use-After-Free को नियंत्रित करने के लिए हमें पहले एक नया कनेक्शन प्रारंभ करना होगा।
चूंकि हम tls_write() का शोषण करना चाहते हैं, हमें पहले एक नया TLS सत्र शुरू करना होगा।
इसलिए पहले हम EHLO कमांड भेजते हैं, उसके बाद TLS कनेक्शन शुरू करने के लिए STARTTLS भेजते हैं।
फिर more को 1 बनाने के लिए हम एक कमांड को पाइपलाइन करते हैं, और अंतिम कमांड एक NOOP का आधा होगा।
हम TLS कनेक्शन बंद कर देते हैं और NOOP कमांड का शेष भाग भेजते हैं।
अब हम फिर से EHLO भेजते हैं, जो smtp_reset को कॉल करवाएगा और हमारे बफ़र को मुक्त कर देगा।
अब हमें tls_write() का फिर से उपयोग करने में सक्षम होने के लिए एक और TLS कनेक्शन शुरू करना होगा।
हम STARTTLS भेजते हैं।
अब सर्वर को कोई भी कमांड भेजने पर प्रतिक्रिया लौटाने के लिए tls_write() कॉल होगा।
लेकिन... server_corked में अभी भी मुक्त मेमोरी में कहीं एक पॉइंटर है।
और वह डेटा अन्य फ़ंक्शन द्वारा उपयोग किया जा सकता है क्योंकि यह मुक्त है...इसलिए हमारा gstring स्ट्रक्चर यादृच्छिक बाइनरी डेटा से दूषित हो जाएगा।
UAF ट्रिगर करने का यह परिणाम है:
gef➤ p *corked
$1 = {
size = 0x54595c9c,
ptr = 0xa7e800ba,
s = 0x7e35043433160bd3 <error: Cannot access memory at address 0x7e35043433160bd3>
}
gef➤ p corked
$2 = (gstring *) 0x555ad3be1b58
gef➤
यह स्ट्रक्चर इसी तरह से होता है जब हम STARTTLS के बाद अपने कमांड के लिए tls_write() में प्रवेश करते हैं।