Как получить root на Ubuntu 20.04
MoodyЯ большой фанат Ubuntu, поэтому я хочу помочь сделать его максимально безопасным. В последние месяцы я потратил довольно много времени на поиск уязвимостей в системных службах. Большинство из них разочаровали, так как оказались низкой степени критичности. Ubuntu - это открытый исходный код, обозначающий, что люди, изучавшие его до меня, скорее всего, уже нашли все самые простые (и не очень) уязвимости.
Эта статья посвящена необычайно легкому способу повышения привилегий в Ubuntu (но, не смотря на это, я не хочу, чтобы она произвела у вас впечатление, будто ОС полна тривиальных ошибок безопасности). С помощью нескольких простых команд в терминале и щелчков мыши обычный пользователь может создать для себя учетную запись администратора. Я записал короткое демонстрационное видео:
Это необычно, что уязвимость такого уровня оказалась такой простой. Порой я писал тысячи строк кода для эксплуатации современных операционных систем. Многие эксплойты включают в себя хитрые уловки, такие как повреждение памяти для подделки объектов в куче или замена файлов символической ссылкой с точностью до микросекунд при использовании уязвимости TOCTOU. В наши дни редко найти уязвимость, для использования которой не требуются навыки программирования. Я думаю, что вы легко поймете, о чем пойдет речь дальше, даже если у вас нет предварительных знаний о том, как работает Ubuntu, или опыта исследования безопасности.
Отказ от ответственности: если кто-то попытается эксплуатировать эту уязвимость, то ему будет необходим доступ к сеансу графического рабочего стола системы, поэтому эта проблема затрагивает только пользователей настольных компьютеров.
Пошаговая эксплуатация
Откройте терминал и создайте символическую ссылку в своем домашнем каталоге:
ln -s /dev/zero .pam_environment
(Если это не сработает из-за того, что файл с именем .pam_environment уже существует, просто переименуйте исходный файл, чтобы у вас была возможность вернуться к нему позже)
Затем откройте раздел «Регион и язык» в настройках системы и попробуйте изменить язык. Диалоговое окно зависнет, проигнорируйте это и вернитесь в терминал. На данном этапе процесс с именем accounts-daemon потребляет 100% ядра ЦП, поэтому компьютер может затормозить и начать нагреваться.
Удалите в терминале ранее созданную символическую ссылку (иначе вы заблокируете свою учетку).
rm .pam_environment
Следующим шагом является отправка сигнала SIGSTOP демону accounts-daemon, чтобы он не перегружал ядро процессора. Но для этого нужно узнать идентификатор процесса (PID). В видео я делаю это, запустив top - утилиту для мониторинга запущенных процессов. Поскольку account-daemon работает в бесконечном цикле, вы сможете его найти в начале списка. Другой способ найти нужный PID - использовать утилиту pidof:
$ pidof accounts-daemon 597
Вооружившись PID'ом account-daemon'а, вы можете воспользоваться kill для отправки сигнала SIGSTOP:
kill -SIGSTOP 597
Теперь компьютер может сделать передышку.
Вот решающий этап. Вы собираетесь выйти, но перед тем, как это сделать, сначала нужно установить таймер для сброса учетных записей (accounts-daemon) после выхода из системы. В противном случае компьютер заблокируется, и эксплойт не сработает. (Не беспокойтесь, если это произойдет - после перезагрузки все вернется в норму.). Вот как установить таймер:
nohup bash -c "sleep 30s; kill -SIGSEGV 597; kill -SIGCONT 597"
Утилита nohup - это простой способ оставить скрипт запущенным после выхода из системы. Эта команда указывает запустить bash-скрипт, который выполняет три действия:
- Заснуть на 30 секунд. (Нужно выделить для себя достаточно времени, чтобы выйти из системы. Я установил его на 10 секунд для видео.)
- Отправить account-daemon сигнал SIGSEGV, который приведет к сбою.
- Отправить account-daemon сигнал SIGCONT для деактивации SIGSTOP, который отправили ранее. SIGSEGV не вступит в силу, пока не будет получен SIGCONT.
Выйдите из системы и подождите несколько секунд, пока SIGSEGV не взорвется. Если эксплойт сработает успешно, откроется ряд диалоговых окон, которые позволят вам создать новую учетную запись пользователя. Новая учетная запись пользователя является учетной записью администратора. (В видео я запускаю id, чтобы показать, что новый пользователь является членом группы sudo, что означает, что у него есть root-привилегии)

Как это работает?
Не уходите, дочитайте до конца! Даже если у вас нет предварительных знаний о том, как работает Ubuntu (а, если точнее, то GNOME), я думаю, что смогу доходчиво объяснить вам эту уязвимость. На самом деле здесь присутствуют две ошибки. Первая находится в accountservice, который представляет собой службу, управляющую учетными записями пользователей на компьютере. Вторая - в GNOME Display Manager (gdm3), который, помимо прочего, обрабатывает экран входа в систему. Я объясню каждый из этих багов ниже.
Отказ в обслуживании (GHSL-2020-187, GHSL-2020-188 / CVE-2020-16126, CVE-2020-16127)
Демон accounts-daemon - это системная служба, которая управляет учетными записями пользователей на машине. Он умеет создавать новые учетные записи или измененять пароли пользователей, а также в его полномочиях менее важные для безопасности вещи, например изменение иконки пользователя или языка системы. Демоны - это программы, которые работают в фоновом режиме и не имеют собственного пользовательского интерфейса. Диалоговое окно системных настроек может взаимодействовать с демоном учетных записей через систему сообщений, известную как D-Bus.


В эксплойте я использую диалоговое окно системных настроек, чтобы изменить язык. Обычному пользователю можно изменять этот параметр в рамках своей учетной записи - права администратора не требуются. Под капотом диалоговое окно системных служб отправляет команду org.freedesktop.Accounts.User.SetLanguage демону accounts-daemon через D-Bus.
Оказывается, Ubuntu использует модифицированную версию accountservice, которая включает дополнительный код, отсутствующий в исходной версии поддерживаемой freedesktop. Патч Ubuntu добавляет функцию is_in_pam_environment, которая ищет файл с именем .pam_environment в домашнем каталоге пользователя и пытается его прочитать. Уязвимость отказа в обслуживании работает, делая .pam_environment символической ссылкой на /dev/zero. /dev/zero - это специальный файл, которого на самом деле не существует на диске. Источник нулевых байтов (ASCII NUL, 0x00), при чтении которого никогда не достигается его конец. Когда is_in_pam_environment пытается прочитать .pam_environment, он редиректится на /dev/zero по символической ссылке, а затем застревает в бесконечном цикле, потому что/dev/zero бесконечен.
У этой ошибки есть вторая часть. Эксплойт вызывает сбой демона учетных записей, отправляя ему SIGSEGV. Разумеется, обычному пользователю нельзя допускать вызов сбоя системной службы? Они не должны этого делать, но account-daemon случайно позволяет пользователю сделать это, сбрасывая привилегии перед тем, как он начнет читать пользовательское .pam_environment. Сброс привилегий означает, что демон временно лишается своих root-привилегий, принимая вместо этого более низкие привилегии пользователя. По иронии судьбы, это мера предосторожности, цель которой - защитить демон от злонамеренного пользователя при помощи символической привязки .pam_environment к /etc/shadow, который является очень секретным файлом, не разрешененным для чтения обычным пользователям. К сожалению, если что-то работает некорректно, он также дает пользователю разрешение на отправку сигналов демона, поэтому мы можем отправлять account-daemon SIGSEGV.
Повышение привилегий gdm3 из-за неотвечающего демона учетных записей (GHSL-2020-202 / CVE-2020-16125)
GNOME Display Manager (gdm3) - это фундаментальный компонент пользовательского интерфейса Ubuntu. Он отвечает за запуск и остановку пользовательских сеансов, при входе и выходе из системы. А также управляет экраном входа в систему.

Еще одна вещь, которой занимается gdm3, - это первоначальная настройка нового компьютера. Когда вы устанавливаете Ubuntu на новый компьютер, первое, что вам нужно сделать, это создать учетную запись пользователя. Первая учетная запись пользователя должна быть администратором, чтобы вы могли продолжить настройку машины, например, подключиться к Wi-Fi и установить необходимые приложения. Вот скриншот экрана начальной настройки (взят из видео об эксплойте):

Диалоговое окно, которое вы видите на скриншоте, представляет собой отдельное приложение, называемое gnome-initial-setup. Он запускается gdm3, когда в системе нет учетных записей пользователей, это очевидный сценарий во время первоначальной настройки нового компьютера. Как gdm3 проверяет, сколько пользователей в системе? Вы, наверное, уже догадались: спросив account-daemon! Так что же произойдет, если account-daemon не отвечает? Соответствующий код здесь.
Он использует D-Bus, чтобы опросить account-daemon и узнать сколько существует пользователей, но поскольку account-daemon не отвечает, вызов метода D-Bus завершается ошибкой из-за тайм-аута. (В моем тестировании тайм-аут занял около 20 секунд.) Из-за ошибки тайм-аута код не установит значение priv->have_existing_user_accounts. Так уж вышло, что значение по умолчанию priv->have_existing_user_accounts, поэтому теперь gdm3 считает, что учетных записей пользователей ноль, и запускает gnome-initial-setup.
Как я это нашел?
Должен признаться: я обнаружил этот баг совершенно случайно. Вот сообщение, которое я отправил своим коллегам 14 октября, примерно в 22:00 по московскому времени:
Вот что произошло: я обнаружил парочку уязвимостей отказа в обслуживании в accountservice. Их критичность я посчитал низкой, но все же написал о них для отправки отчета об уязвимостях в Ubuntu. Около 18:00 я прекратил работу и закрыл ноутбук. Позже вечером я открыл ноутбук и обнаружил, что мой аккаунт заблокирован. Я экспериментировал с символической ссылкой .pam_environment и забыл удалить ее перед закрытием. Ничего страшного: я использовал Ctrl-Alt-F4, чтобы открыть консоль, вошел в систему (на вход в консоль не повлияла служба учетных записей DOS) и убил accounts-daemon при помощи SIGSEGV. Мне не нужно было использовать sudo из-за уязвимости, связанной со сбросом привилегий. Я наблюдал за появлением диалоговых окон gnome-initial-setup и был поражен, обнаружив, что я смог создать нового пользователя с правами администратора.
К сожалению, когда я попытался воспроизвести эту же последовательность позже, мне не удалось заставить ее снова работать. Я проверил системные логи на предмет подсказок, но там было мало информации, потому что у меня не были включены отладочные сообщения gdm. Разработанный мной эксплойт требует, чтобы пользователь вышел из своей учетной записи, но я определенно не делал этого вечером 14 октября. Так что остается загадкой, как у меня выскочила ошибка в тот вечер.
Позже тем же вечером я отправил своим (находящимся в США) коллегам новые сообщения с описанием того, что произошло. Разговор о диалоговых окнах помог мне вспомнить кое-что, что я заметил недавно. Многие системные службы, которые я просматривал, используют policykit, чтобы проверить, авторизован ли клиент. Я заметил файл под названием gnome-initial-setup.pkla, который представляет собой набор конфигураций политик, предоставляющих пользователю с именем gnome-initial-setup возможность выполнять ряд важных для безопасности вещей, таких как монтирование файловых систем и создание новых пользователей. Поэтому я сказал своим коллегам: «Интересно, имеет ли это какое-то отношение к gnome-initial-setup», и Бас Альбертс почти сразу же поддержал гипотезу, которая оказалась верной: «Вы обманом заставили gdm запустить gnome-initial-setup, я полагаю, что такое может произойти, если сеанс gdm не может проверить, что учетная запись уже существует».
После этого нужно было просто найти код в gdm3, который запускает gnome-initial-setup, и выяснить, как его запустить, когда account-daemon не отвечает. Я обнаружил, что соответствующий код срабатывает, когда пользователь выходит из системы.
И это история о том, как конец моего рабочего дня стал началом нулевого дня!
Прочитать оригинал этого материала на английском можно здесь.
Cybred - канал об информационной безопасности и конкурентной разведке, вдохновленный идеями олдскульных андеграундных интернет-сообществ о свободе распространения информации в сети и всеобщей взаимопомощи.