Как мы нашли уязвимость в почтовом сервере банка и чем она грозила

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Мы часто проводим пентесты для банков и других финансовых организаций. Так же часто обнаруживаем уязвимости разного уровня критичности. Этот пост — про один из таких кейсов.

Недавно, проверяя защищенность веб-ресурсов банка, мы нашли уязвимость в почтовом сервере Exim 4.89, которая может приводить к удаленному выполнению кода. Уязвимость известна как CVE-2018-6789. Применив PoC-эксплойт, мы получили Reverse Shell к удаленной машине, а затем и доступ к веб-сайту банка.



Естественно, мы заинтересовались, почему такая эксплуатация уязвимости стала возможной.

Откуда взялась CVE-2018-6789


Если совсем коротко, уязвимость существует из-за ошибки вычисления длины буфера в функции base64.c:b64decode, используемой в Exim. Подробнее об этом можно прочитать здесь (на английском).

Если подать на вход строку особой длины, можно перезаписать один байт информации и с помощью несложных действий изменить команды сервера, тем самым выполнив произвольный код (RCE).

Exim выделяет буфер размером 3*(len/4)+1 байт для хранения декодированных данных. Однако если на вход функции подать некорректную по длине base64-строку, например длинной 4n+3, то Exim выделит 3n+1 байт под буфер. Но при этом запишет в буфер 3n+2 байта данных. Это вызывает перезапись одного байта в куче.

В Exim существуют функции store_malloc_3 и store_free_3 — «обертки» для функций malloc и free из Glibc. Glibc выделяет большой блок данных, затем сохраняет в первых 0x10 байтах свои метаданные и возвращает указатель на память куда уже пользователь может записывать свои данные. Вот как это выглядит:


Метаданные включают размер предыдущего блока (тот, который находится в памяти выше), размер текущего блока и некоторые флаги. Первые три бита размера используются для хранения флагов. В примере размер 0x81 подразумевает, что текущий фрагмент составляет 0x80 байтов, а предыдущий фрагмент уже используется.

Освобожденные блоки, когда-то используемые в Exim, помещаются в двусвязный список. Glibc поддерживает его в соответствии с флагами и объединяет смежные освобожденные фрагменты в более крупный фрагмент, чтобы избежать фрагментации. Для каждого запроса на выделение памяти Glibc проверяет эти фрагменты в порядке FIFO и повторно их использует.


Для повышения производительности Exim использует свою надстройку для управления памятью; в ее основе лежит структура storeblock. Основная особенность storeblock заключается в том, что каждый из них имеет размер не менее 0x2000 байт, что становится ограничением для эксплуатации. Обратите внимание, что storeblock также является данными блока. Вот как это выглядит в памяти:


Команды, поддерживаемые почтовым сервером, для организации данных в куче:

  • EHLO hostname. При каждом выполнении команды EHLO hostname сохраняется в переменную sender_host_name. Соответственно, сначала выполняется store_free для старого имени и store_malloc для нового.
  • Любая неопознанная команда. Для каждой нераспознанной команды, составленной из непечатаемых символов, Exim выделит буфер для конвертации ее в читаемые символы.
  • AUTH. В большинстве процедур аутентификации Exim использует кодировку base64 для связи с клиентом. Строка кодирования и декодирования хранится в буфере, выделенном store_get (). Буфер под закодированную и декодированную строку выделяется с помощью store_get().
  • Reset в командах EHLO/HELO, MAIL, RCPT. Когда команды завершаются корректно, Exim вызывает smtp_reset. Она в свою очередь вызывает store_reset для того, чтобы сбросить цепочку блоков до «точки сброса». Это значит, что все storeblock-и выделенные store_get после последней команды будут освобождены.

Как эксплуатируется уязвимость


Для того что использовать однобайтовое переполнение в куче, у нас должна быть возможность освободить блок данных, который находится под декодированной base64 строкой. Для этого подойдет sender_host_name.

Необходимо сформировать кучу таким образом, чтобы оставить освобожденным блок данных над блоком, содержащим sender_host_name.


Для этого нужно:

1. Положить большой блок в unsorted bin. Прежде всего, мы отправляем сообщение EHLO с именем хоста избыточной длины, чтобы он выделил и освободил кусок длиной 0x6060 в unsorted bin.

2. Выделить первый storeblock. Затем мы отправляем нераспознаваемую команду для того, чтобы вызвать store_get () и выделяем storeblock внутри освобожденного фрагмента.

3. Выделить второй блок и освободить первый. Отправляем EHLO, чтобы получить второй блок. Первый блок освобождается последовательно из-за smtp_reset, вызываемого после завершения EHLO.

После того как куча подготовлена, мы можем использовать ее для перезаписи исходного размера, блока. Модифицируем 0x2021 на 0x20f1, что немного расширяет блок.



4. Отправить данные base64 и переполнить 1 байт на куче. Запускаем команду AUTH для отправки данных base64.

5. Создать и отправить строку подходящего размера. Поскольку мы расширили блок данных, начало следующего фрагмента теперь будет лежать где-то внутри. Теперь нам нужно исправить его для того, чтобы пройти проверку целостности Glibc. Мы отправляем сюда еще одну строку base64.

6. Освободить расширенный блок. Чтобы контролировать содержимое расширенного блока, нам нужно сначала освободить блок, потому что мы не можем редактировать его напрямую. То есть мы должны отправить новое сообщение EHLO, чтобы освободить старое имя хоста. Однако обработка команды EHLO вызывает smtp_reset после ее успешного выполнения. Это может привести к прерыванию программы или ее аварийному завершению. Чтобы этого избежать, мы отправляем недопустимое имя хоста, например а+.

7. Перезаписать следующий указатель перекрывающегося блока storeblock.


После того, как фрагмент освобожден, мы можем получить его с помощью AUTH и перезаписать часть перекрывающегося блока storeblock. Здесь мы используем прием, называемый «частичной записью». Благодаря этому мы можем изменить указатель, не нарушая ASLR. Мы частично изменили следующий указатель на блок, содержащий строки ACL. Строки ACL указываются набором глобальных указателей, например uschar *acl_smtp_helo;

Эти указатели инициализируются в начале процесса Exim и устанавливаются в соответствии с конфигурацией. Например, если в конфигурации есть строка acl_smtp_mail = acl_check_mail, указатель acl_smtp_mail указывает на строку acl_check_mail.

Каждый раз, когда сервер получает команду MAIL FROM, Exim выполняет проверку ACL, которая сначала раскрывает acl_check_mail. При раскрытии, если Exim встретит строку $ {run {cmd}}, то он попытается выполнить команду “cmd”,, поэтому удаленный злоумышленник может добиться выполнения кода.

8. Сбросить storeblock и получить storeblock, содержащий ACL. Теперь блок ACL находится в цепочке блоков. Он будет освобожден после выполнения smtp_reset (), а затем мы сможем получить его снова, выделив несколько блоков.

9. Перезаписать строки ACL и запустить проверку ACL. Наконец, мы перезаписываем весь блок, содержащий строки ACL. Теперь мы отправляем такие команды, как EHLO, MAIL, RCPT для запуска проверки ACL.

К слову, эксплуатацию уязвимости облегчила выключенная по какой-то неведомой нам причине ASLR.

Какие проблемы были у заказчика


Первая — отсутствие управления обновлениями. Из-за того, что использовалась старая версия Exim, удалось организовать компрометацию системы. Чтобы избежать этого, мы рекомендуем организовать регулярную проверку и установку обновлений безопасности на компоненты информационной инфраструктуры.

Проверку наличия новых обновлений безопасности и критических обновлений рекомендуем проводить не реже раза в месяц. Для проверки обновлений лучше использовать сайты/рассылки производителей оборудования или информацию с репозиториев.

Также в банке отсутствовал процесс управления уязвимостями, из-за чего уязвимость не была вовремя обнаружена. Исправить ситуацию помогло бы использование специализированных сканеров уязвимостей — например, OpenVAS, Nessus, xSpider и т. п. А также проведение регулярных тестирований на проникновение и контроль сроков устранения уязвимостей.

И последнее, но немаловажно. В банке отсутствовал процесс управления изменениями. Все изменения делались администраторами сразу в production-среде. Следовательно, никто это не контролировал и не мониторил. Это и привело к тому, что на сервере был выключен ASLR.

Вывод


Несколько, казалось бы, несвязанных между собой нарушений привели к тому, что сайт банка был скомпрометирован. Этим могли воспользоваться злоумышленники, чтобы, например, поменять реквизиты банка на собственные.

История закончилась хорошо. После компрометации мы сразу же оповестили клиента о ситуации. Банк в срочном порядке обновил Exim-сервер до актуальной версии, для которой уязвимость уже не актуальна. Однако если бы уязвимость была выявлена не во время пентеста, а реальными злоумышленниками, исход мог бы быть другим.
Источник: https://habr.com/ru/company/itglobalcom/blog/523814/


Интересные статьи

Интересные статьи

Последнее время в Telegram все чаще обсуждаются темы пробива людей и утечек персональных данных. Мне стало интересно, а насколько сама экосистема Telegram устойчива к подобным утечкам. Под...
В последние несколько недель, по стечению обстоятельств на работе и в сторонних проектах, я узнал много о веб-шрифтах, а также много нового о Google Fonts в частности. Благодаря этому я могу дать...
Совершая очередную транзакцию в моем любимом банке Тинькофф, получил уже привычное сообщение: Никому не говорите код: 3131! Перевод с карты ****. Сумма ***.00 RUB Если будут спрашивать — я вам ...
На прошлой неделе столкнулся с крайне неприятным фактом. Зайдя на свой сайт, обнаружил, что он переадресовывает меня на неведомый мне ресурс, на который крайне сильно ругается антивирус Dr. Web ...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...