
استغلال لـ Exim4 4.93 CVE-2020-28018
اكتشف فريق Qualys ونشر 21 ثغرة أمنية خطيرة في خادم البريد Exim4.
في الرابط التالي، يقدمون معلومات تقنية مفصلة عن الثغرات واستغلالها.
https://www.qualys.com/2021/05/04/21nails/21nails.txt
في هذا المستودع، نركز على CVE-2020-28018 الذي يسمح بتنفيذ الأوامر عن بُعد عن طريق استغلال ثغرة Use-after-free.
تقع الثغرة في الدالة tls_write() في الملف 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)
{
size_t olen = len;
int outbytes, error;
SSL * ssl = ct_ctx
? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
static gstring * server_corked = NULL;
gstring ** corkedp = ct_ctx
? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
gstring * corked = *corkedp;
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.
We support callouts done by the server process by using a separate client
context for the stashed information. */
/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
a store reset there, so use POOL_PERM. */
/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
if ((more || corked))
{
#ifdef SUPPORT_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
#endif
corked = string_catn(corked, buff, len);
#ifdef SUPPORT_PIPE_CONNECT
store_pool = save_pool;
#endif
if (more)
{
*corkedp = corked;
return len;
}
buff = CUS corked->s;
len = corked->ptr;
*corkedp = NULL;
}
for (int 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 olen;
}
يتيح SMTP إرسال أوامر "PIPELINED" (rfc2920) وهي القدرة على تجميع عدة أوامر وإرسالها إلى الخادم معًا بدلاً من واحد تلو الآخر لتحسين زمن الانتظار. يتم تسلسل الأوامر مفصولة بـ \n. وبالمثل، يقوم الخادم بتجميع الردود وإرسالها بعد الأمر الأخير الذي ينتهي بـ \r\n.
تحتوي الدالة tls_write على متغير محلي وثابت، server_corked، من النوع gstring *. هذه البنية هي المسؤولة عن تسلسل الردود على أوامر PIPELINED.
البنية gstring، المُعرَّفة في structs.h، تسمح بإنشاء سلاسل ديناميكية.
/* Growable-string */
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;
لكل أمر PIPELINED، سيتم استدعاء tls_write مع more=1. في الأمر الأول، corked هو NULL وبالتالي سيتم حجز ذاكرة لتخزين الرد المتسلسل. في buff يوجد الرد الجديد، وفي corked الرد المتراكم ويكون في البداية NULL.
corked = string_catn(corked, buff, len);
يحتوي Exim4 على مدير ذاكرة ديناميكي خاص به (allocator) سيتم شرحه لاحقًا. تقوم الدالة string_catn بتسلسل الردود السابقة والجديدة. إذا كانت السلسلة الجديدة لا تتسع في المساحة المحجوزة، تحاول توسيعها أو إذا لم يكن ذلك ممكنًا، يتم حجز كتلة جديدة يتم نسخ السلسلة السابقة إليها.
تقوم الأوامر RST, STARTTLS, MAIL, EHLO بتنفيذ الدالة smtp_reset(). تعيد هذه الدالة تعيين جلسة SMTP عن طريق مسح متغيرات الحالة وتحرير كتل الذاكرة حتى reset_point. المشكلة تكمن في أن المتغير الثابت والمحلي server_corked في tls_write() لا يُعاد تعيينه إلى NULL، بحيث في التنفيذ التالي للدالة، نظرًا لأن corked ليس NULL، سيتم تسلسل هذه البنية المحررة بالفعل مع الرد الجديد.
حزم Debian/Ubuntu تأتي مُجمَّعة مع مكتبة GnuTLS. قم بتشغيل الملف exim_openssl_build.sh لتجميع Exim باستخدام OpenSSL.
sudo /usr/sbin/exim -bd -d -v
TODO
TODO