El equipo de Qualys ha descubierto y publicado 21 vulnerabilidades críticas en el servidor de correo Exim4.
En el siguiente enlace proporcionan información técnica detallada de las vulnerabilidades y de su explotación.
https://www.qualys.com/2021/05/04/21nails/21nails.txt
En este repositorio nos centramos en CVE-2020-28018 que permite ejecución remota de código explotando una vulnerabilidad Use-after-free.
- STARTTLS activado (por defecto)
- PIPELINING activado (por defecto)
- X_PIPE_CONNECT desactivado (por defecto antes de Exim 4.94)
- Exim4 compilado con la librería OpenSSL. (Require recompilar, explicado más abajo)
- Todas las pruebas se han realizado con Exim 4-4.93 del repositorio source de Ubuntu 20.04.
La vulnerabilidad se encuentra en la función tls_write() en el fichero 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 permite enviar comandos "PIPELINED" (rfc2920) que no es más que la capacidad de agrupar varios comandos y mandarlos al servidor de forma conjunta en lugar de uno a uno para mejorar la latencia. Los comandos se concatenan separados por \n. De igual forma, el servidor agrupa las respuestas y las manda después del último comando que terminará con \r\n.
La función tls_write tiene una variable local y estática, server_corked, de tipo gstring *. Esta estructura es la encargada de ir concatenando las respuestas a comandos PIPELINED.
La estructura gstring, definida en structs.h, permite crear cadenas dinámicas.
/* 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;
Por cada comando PIPELINED se llamará a tls_write con more=1. En el primer comando corked es NULL y por tanto se reservará memoria para almacenar la respuesta concatenada. En buff se encuentra la nueva respuesta y en corked la respuesta acumulada en principio NULL.
corked = string_catn(corked, buff, len);
Exim4 tiene su propio gestor de memoria dinámica (allocator) que se explicará más adelante. La función string_catn se encarga de concatenar las respuestas anteriores y la nueva. Si la nueva cadena no cabe en el espacio reservado intenta agrandarlo o si no es posible se reserva un nuevo bloque donde se copia la cadena anterior.
Los comandos RST, STARTTLS, MAIL, EHLO ejecutan la función smtp_reset(). Esta función resetea la sesión SMTP limpiando las variables de estado y libera los bloques de memoria hasta el reset_point. El problema radica en que la variable estática y local server_corked en tls_write() no se resetea a NULL de forma que en la próxima ejecución de la función, al ser corked distinto de NULL, se concatenará esta estructura ya liberada con la nueva respuesta.
Los paquetes de Debian/Ubuntu vienen compilados con la librería GnuTLS. Ejecuta el fichero exim_openssl_build.sh para compilar Exim con OpenSSL.
sudo /usr/sbin/exim -bd -d -v
- -bd Ejecuta exim como demonio.
- -d debugging
- -v verbose
TODO
TODO