CVE Breakdown
August 6, 2024

SSH RCE via Race Condition (CVE-2024-6387)

1 июля, исследователи Qualys обнаружили как добиться удаленного исполнения кода в SSH 8.5p1 через состояние гонки в обработчике сигнала SIGALRM.

Состояние гонки - когда несколько частей программы могут одновременно изменять один и тот же ресурс, без какой-либо синхронизации в формате "сначала меняю значение Я, а потом ТЫ"

Что интересно, эта уязвимость уже была в 2006, получив CVE-2006-5051 и будучи исправленной патчем 4.4p1

deja vu i've been in this place before

но потом "случайно" вернулась в прод, от чего Qualys прозвали уязвимость regreSSHion.


Опасный обработчик сигнала

В конфиге для sshd есть опция LoginGraceTime, описывающая за сколько секунд пользователь должен успеть аутентифицироваться перед его отключением от сервера. По умолчанию, за 120 секунд.

Если пользователь аутентифицируется больше 120 секунд, то асинхронно вызывается SIGALRM обработчик для прерывания соединения.

Однако, этот обработчки также прерывает синхронные функции, небезопасные для асинхронных сигналов (non-async-signal-safe), такие как syslog()

В Qualys обнаружили, что во время состояния гонки возможно добиться повреждения кучи с FILE структурой и перенаправить исполнения программы.

как себя ощущает syslog() при каждой попытке эксплуатации


Повреждение кучи

В управлении памятью есть два основных концепта

  • стек, с упорядоченной памятью
  • куча (heap), когда информация хранится как попало

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

В итоговой куче должен быть свободный кусок памяти в 8КБ и после свободный мелкий кусок в 320байт, разделенные "чанк барьерами" (barrier chunk), что позволяют создать отдельные свободные куски памяти

Прыгаем на подключение к серверу.

  1. атакующий превышает время для аутентификации во время подключения, указанное в LoginGraceTime
  2. вызывается асинхронный SIGALRM обработчик, что вызывает синхронный syslog()

Здесь атакующий встречается с новым испытанием, ведь теперь ему надо попасть в исполнение malloc() внутри syslog(), между 4327 и 4339 строкой, когда malloc() аллоцировал память, но не успел сдвинуть её.

Что приводит к делению доступного нам куска памяти в 8КБ на 2 чанка по 4КБ. В одном аллоцированная память, а другой становится "свободным остатком"

И тут происходит странная ситуация. Прервав исполнение malloc(), "свободный чанк" привязывается к списку "свободной для перезаписи памяти", в то время как атакующий всё ещё имеет доступ к этой области памяти.

Зная, что эта память перезапишется, атакующий искусственно увеличивает размер "свободного для перезаписи" чанка, чтобы тот захватывал мелкий кусок свободной памяти в 320бит, который был подготовлен ещё в начале атаки.

Думаешь хватит сложностей для атаки? Не тут то было

Эти все операции должны произойти перед тем, как асинхронный обработчик SIGALRM вызовет небезопасные для асинхронного вызова - синхронные функции __tzfile_read() и fopen()

Если всё сошлось, то

  1. fopen() аллоцирует свою FILE структуру в наш мелкий кусок памяти на 320бит
  2. __fread_unlocked() перезапишет "свободный кусок памяти" с 4КБ буфером на чтение, затрагивая часть FILE структуры от fopen()

3. атакующий, имея доступ к буферу, перезаписывает часть FILE структуры и дальше, ещё более душно, перенаправляет flow исполнение программы куда ему нужно для исполнения кода

Вот и всё. Всего-то пару операций, туда-сюда и ты исполнился от рута!

Ладно, давай откроем форточку и суммируем атаку по картинке ниже


Так ли страшно?

Кроме того, что на системе должен быть glibc, большую роль играет архитектура процессора.

Для 32-битных систем

  • 3-4 часа для победы в гонке за ~10 000 попыток, при 100 соединений в окне 120 секунд
  • 6-8 часов чтобы найти нужный адрес и 1 раз исполнится от рута
    • спасибо ASLR, за рандомизацию стека, кучи, кода библиотек и программы, усложняя атакующем находить их в памяти

Для 64-битных систем

  1. Нет подтверждения, что оно возможно
  2. Адресов больше (32бит = 2^32 < 64бит = 2^48)
  3. _

Теперь точно всё. Если твой голод подобных разборов не утолился, то прочитай мой более простой разбор нашумевшего бэкдора в библиотеке xz

Появились вопросы по уязвимости? Читай разбор от Qualys

Появились вопросы не по уязвимости? Пиши @w0ltage