В этой статье я расскажу об уязвимости в агенте пересылки сообщений Exim. Найденная брешь позволяет атакующему выполнить произвольный код на целевой системе, что само по себе очень опасно, а если Exim был запущен от рута, то успешная эксплуатация позволяет получить максимальный контроль над системой.
Я уже дважды писал об RCE в этом почтовом сервере: один раз — в 2017 году, второй — в 2018-м. Оба раза для успешной эксплуатации нужно было разбираться со смещениями, кучами и прочей бинарщиной. В этот раз для проведения атаки достаточно просто отправить письмо через уязвимый Exim на специально сформированный адрес, содержащий пейлоад.
Если вкратце, то атака основана на внедрении произвольных сущностей в expanded strings, в заголовки RCPT TO
и MAIL FROM
. Она позволяет злоумышленнику передать специально сформированную строку как email-адрес, и та будет интерпретирована почтовым сервисом как системная команда.
Баг обнаружили специалисты из Qualys в конце мая этого года. Он получил номер CVE-2019-10149 и затрагивает все версии Exim с 4.87 до 4.91 включительно.
Для создания тестового окружения воспользуемся контейнером 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
Важный параметр — 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, который я ставил из репозитория 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
.
7659: uschar * 7660: expand_string(uschar * string) 7661: { 7662: return US expand_cstring(CUS string); 7663: }
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
.
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».
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее
1 год7690 р. |
1 месяц720 р. |
Я уже участник «Xakep.ru»
Читайте также
Последние новости