Отправляем команды. Как заставить популярный серверный почтовик выполнять произвольный код

В этой статье я расскажу об уязвимости в агенте пересылки сообщений Exim. Найденная брешь позволяет атакующему выполнить произвольный код на целевой системе, что само по себе очень опасно, а если Exim был запущен от рута, то успешная эксплуатация позволяет получить максимальный контроль над системой.

Я уже дважды писал об RCE в этом почтовом сервере: один раз — в 2017 году, второй — в 2018-м. Оба раза для успешной эксплуатации нужно было разбираться со смещениями, кучами и прочей бинарщиной. В этот раз для проведения атаки достаточно просто отправить письмо через уязвимый Exim на специально сформированный адрес, содержащий пейлоад.

Если вкратце, то атака основана на внедрении произвольных сущностей в expanded strings, в заголовки RCPT TO и MAIL FROM. Она позволяет злоумышленнику передать специально сформированную строку как email-адрес, и та будет интерпретирована почтовым сервисом как системная команда.

 

Стенд

Для создания тестового окружения воспользуемся контейнером Docker. На момент публикации уязвимости пакеты Exim, которые лежали в репозитории Debian, содержали данную брешь. Они уже запатчены, поэтому нам нужно будет собрать уязвимую версию из исходников.

$ docker run -it --rm -p25:25 --name=eximrce --hostname=eximrce --cap-add=SYS_PTRACE --security-opt seccomp=unconfined debian /bin/bash

Расшариваем 25-й порт наружу, чтобы в дальнейшем можно было протестировать удаленную атаку. Помимо этого, добавляем флаги, чтобы можно было отлаживать приложение.

Теперь устанавливаем необходимые зависимости для успешной компиляции.

$ apt-get install -y exim4 build-essential git libdb5.3-dev libpcre3-dev libgnutls28-dev libgcrypt-dev wget netcat nano procps gdb

Обрати внимание, что я установил Exim4 из репозиториев. Это нужно для того, чтобы не возиться с конфигурационным файлом, добавлением пользователей и прочими приготовлениями.

Выполняем базовую настройку почтового сервера.

$ dpkg-reconfigure exim4-config

Первичная настройка Exim4

Важный параметр — Domains to relay mail for. Запомни его, я вернусь к нему на этапе удаленной эксплуатации.

Теперь воспользуемся репозиторием Exim4 на GitHub и клонируем последнюю уязвимую ветку — 4.91.

$ git clone --depth=1 -b exim-4_91 https://github.com/Exim/exim.git
$ cd exim/src
$ mkdir Local

Скопируем дефолтный шаблон мейкфайла.

$ cp src/EDITME Local/Makefile

В него нужно внести пачку изменений для того, чтобы скомпилировать максимально соответствующий существующему конфигу бинарник.
Сначала укажем имя пользователя, от которого будет работать Exim. Если ставить из репозиториев, то скрипт установки создает пользователя Debian-exim. Его и указываем.

$ sed -i 's,^EXIM_USER.*$,EXIM_USER=Debian-exim,' Local/Makefile

Отключаем Exim Monitor, так как это графическая утилита для просмотра информации о работе демона и в консоли она нам совершенно ни к чему.

$ sed -i 's,^EXIM_MONITOR=.*$,# EXIM_MONITOR=,' Local/Makefile

Указываем директорию, в которой лежат бинарники.

$ sed -i 's,^BIN_DIRECTORY=.*$,BIN_DIRECTORY=/usr/sbin,' Local/Makefile

Теперь указываем путь до файла конфигурации. Я сгенерировал его через утилиту exim4-config, которая записывает его в /var/lib/exim4/config.autogenerated.

$ sed -i 's,^CONFIGURE_FILE=.*$,CONFIGURE_FILE=/var/lib/exim4/config.autogenerated,' Local/Makefile &&

Дальше идут не особенно важные настройки.

sed -i 's,^# SUPPORT_MAILDIR,SUPPORT_MAILDIR,' Local/Makefile && 
sed -i 's,^# SUPPORT_MAILSTORE,SUPPORT_MAILSTORE,' Local/Makefile && 
sed -i 's,^# SUPPORT_MOVE_FROZEN_MESSAGES,SUPPORT_MOVE_FROZEN_MESSAGES,' Local/Makefile && 
sed -i 's,^# SUPPORT_TLS=,SUPPORT_TLS=,' Local/Makefile && 
sed -i 's,^# USE_GNUTLS=,USE_GNUTLS=,' Local/Makefile && 
sed -i 's,^# TLS_LIBS=-lgnutls,TLS_LIBS=-lgnutls,' Local/Makefile && 
sed -i 's,^# LOOKUP_CDB,LOOKUP_CDB,' Local/Makefile && 
sed -i 's,^# LOOKUP_DSEARCH,LOOKUP_DSEARCH,' Local/Makefile && 
sed -i 's,^# LOOKUP_NIS,LOOKUP_NIS,' Local/Makefile && 
sed -i 's,^# LOOKUP_NISPLUS,LOOKUP_NISPLUS,' Local/Makefile && 
sed -i 's,^# LOOKUP_PASSWD,LOOKUP_PASSWD,' Local/Makefile && 
sed -i 's,^# TRANSPORT_LMTP,TRANSPORT_LMTP,' Local/Makefile && 
sed -i 's,^# AUTH_CRAM_MD5,AUTH_CRAM_MD5,' Local/Makefile && 
sed -i 's,^# AUTH_PLAINTEXT,AUTH_PLAINTEXT,' Local/Makefile && 
sed -i 's,^# HAVE_IPV6,HAVE_IPV6,' Local/Makefile

Изменяем директорию, в которую будет складываться очередь писем для отправки.

$ sed -i 's,^/var/spool/exim,/var/spool/exim4,' Local/Makefile

И последнее изменение — нужно добавить флаг -g, если ты хочешь отлаживать приложение.

$ printf "CFLAGS  += -gn" >> Local/Makefile

Дальше дело за компиляцией.

$ make

Успешная компиляция Exim 4.91

После того как приложение успешно скомпилено, нужно заменить бинарник Exim, который я ставил из репозитория Debian.

$ mv /usr/sbin/exim4 /usr/sbin/exim4_orig && cp -f /root/exim/src/build-Linux-x86_64/exim /usr/sbin/exim4

Стенд готов. Теперь ты можешь запускать демон Exim в качестве сервиса или напрямую из командной строки с выводом информации о работе в консоль.

$ exim4 -bdf -d+all

 

Детали уязвимости и локальная эксплуатация

Сначала я расскажу о самом простом способе эксплуатации — локальном. Попутно разберем, в чем же именно причина уязвимости.

В окружении сервера Exim есть такое понятие, как String Expansion. Грубо говоря, это аналог макросов, как в разных шаблонизаторах. Строки специального вида, которые обрабатываются парсером Exim. Среди множества команд и функций, которые доступны в рамках String Expansion, имеется вызов внешней программы — run.

${run{<команда> <аргументы>}{<string1>}{<string2>}}

Сам парсинг выполняется функцией expand_string.

src/src/expand.c

7659: uschar *
7660: expand_string(uschar * string)
7661: {
7662: return US expand_cstring(CUS string);
7663: }

src/src/expand.c

7640: const uschar *
7641: expand_cstring(const uschar * string)
7642: {
7643: if (Ustrpbrk(string, "$\") != NULL)
7644:   {
7645:   int old_pool = store_pool;
7646:   uschar * s;
7647:
7648:   search_find_defer = FALSE;
7649:   malformed_header = FALSE;
7650:   store_pool = POOL_MAIN;
7651:     s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
7652:   store_pool = old_pool;
7653:   return s;
7654:   }
7655: return string;
7656: }

Среди огромного количества мест, где она вызывается, есть такое место и в deliver_message.

src/src/deliver.c

5505: int
5506: deliver_message(uschar *id, BOOL forced, BOOL give_up)
5507: {
...
6224: #ifndef DISABLE_EVENT
6225:       if (process_recipients != RECIP_ACCEPT)
6226:   {
6227:   uschar * save_local =  deliver_localpart;
6228:   const uschar * save_domain = deliver_domain;
6229:
6230:   deliver_localpart = expand_string(
6231:             string_sprintf("${local_part:%s}", new->address));
6232:   deliver_domain =    expand_string(
6233:             string_sprintf("${domain:%s}", new->address));
6234:
6235:   (void) event_raise(event_action,
6236:             US"msg:fail:internal", new->message);
6237:
6238:   deliver_localpart = save_local;
6239:   deliver_domain =    save_domain;
6240:   }

Как видишь, эта ветка компилируется в случае, когда символическая константа DISABLE_EVENT не определена. Так оно и есть, начиная с версии 4.87 Events — полноправная часть Exim и используются по умолчанию.

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *