Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
POC-CVE-2023-32233 — Использование после освобождения в Netfilter nf_tables при обработке пакетных запросов CVE-2023-32233 | Kitploit
Инструменты/GitHubGitHub/oferchen/poc-cve-2023-32233
Повышение привилегийКриминалистика памятиАнализ уязвимостейЭксплуатацияТестирование на ПроникновениеРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHuboferchen/poc-cve-2023-32233

POC-CVE-2023-32233

Использование после освобождения в Netfilter nf_tables при обработке пакетных запросов CVE-2023-32233

Репозиторий
53623 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Использование после освобождения (Use-After-Free) в Netfilter nf_tables при обработке пакетных запросов

Демонстрация

Demo_CVE-2023-32233

Сведения об уязвимости

Затронутый код происходит из официального ядра Linux с https://kernel.org/ и является частью компонента Netfilter nf_tables (net/netfilter/nf_tables_api.c).

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

В нашем конкретном сценарии мы начинаем с конфигурации Netfilter nf_tables, которая содержит nft_rule с выражением lookup на анонимном nft_set, и где анонимный nft_set содержит некоторые элементы. Затем мы отправляем пакетный запрос, содержащий следующие две базовые операции:

  1. Операция NFT_MSG_DELRULE для удаления nft_rule.
    Обратите внимание, что это также неявно удаляет выражение lookup и анонимный nft_set.
  2. Операция NFT_MSG_DELSETELEM для удаления любого из элементов удалённого анонимного nft_set.

Текущая версия Netfilter nf_tables принимает указанный выше пакетный запрос. Затем она вызывает nf_tables_commit_release(), которая добавляет освобождённые ресурсы в nf_tables_destroy_list. nf_tables_destroy_list затем обрабатывается nf_tables_trans_destroy_work(), которая сначала освобождает ресурсы, связанные с операцией NFT_MSG_DELRULE, вызывая:

root@kitploit:~
nft_commit_release()
    nf_tables_rule_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy() что указывает на nft_lookup_destroy()
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() которая освобождает память, используемую `nft_set`

перед обработкой операции NFT_MSG_DELSETELEM, где ссылка на освобождённый nft_set получается через nft_trans_elem_set() во время следующих вызовов:

root@kitploit:~
nft_commit_release()
    nf_tables_set_elem_destroy()
        nft_set_elem_ext()

В nft_set_elem_ext() выше, местоположение памяти освобождённого nft_set используется для определения местоположения nft_set_ext:

root@kitploit:~
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
                                                   void *elem)
{
        return elem + set->ops->elemsize;
}

для последующих операций. Поэтому, когда значение set->ops->elemsize оказывается повреждённым, некоторое непредвиденное место в памяти может быть интерпретировано как список nft_expr, подлежащих уничтожению:

root@kitploit:~
static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
                                       const struct nft_set *set, void *elem)
{
        struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

        if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
                nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

Методы эксплуатации

Эксплуатация вышеуказанной уязвимости требует выигрыша состояния гонки с nf_tables_trans_destroy_work(), которая выполняется из фонового рабочего потока ядра Linux. Это, по-видимому, усложняет практическую эксплуатацию даже до рассмотрения существующих средств смягчения, таких как укрепление аллокатора slab ядра, рандомизация адресного пространства ядра (KASLR) и, особенно, целостность потока управления (Control-Flow Integrity). Однако приложенный PoC доказывает, что всё ещё возможно добиться достаточно надёжной эксплуатации на практике.

Чтобы использовать уязвимость, нам нужно изменить содержимое памяти из nft_set после его освобождения в nf_tables_rule_destroy(), но до того, как он будет использован в nf_tables_set_elem_destroy(). Оба вызова nf_tables_rule_destroy() и nf_tables_set_elem_destroy() выполняются в рамках одного вызова nf_tables_trans_destroy_work(), который выполняется из фонового рабочего потока ядра Linux. Кроме того, освобождённый фрагмент памяти обычно доступен для повторного использования только на том же ядре CPU.

При гонке с nf_tables_trans_destroy_work() мы увеличиваем свои шансы, добавляя контролируемую задержку для фонового рабочего потока между вызовами nf_tables_rule_destroy() и nf_tables_set_elem_destroy(). Для этого мы вставляем дополнительную операцию по уничтожению другого nft_set, содержащего большое количество элементов. Кроме того, мы держим все остальные ядра CPU занятыми, так что фоновый рабочий поток, скорее всего, будет запланирован на определённом ядре CPU, и мы можем попытаться выделить новую структуру на том же ядре CPU сразу после того, как он освободит nft_set в nf_tables_rule_destroy(). Наша цель — выделить новый nft_set другого типа, чтобы повторно использовать местоположение памяти nft_set, освобождённого в nf_tables_rule_destroy().

Новый тип nft_set выбирается так, чтобы использовать другое значение для set->ops->elemsize. Поэтому, когда фоновый рабочий поток наконец вызывает nf_tables_set_elem_destroy() для обработки операции NFT_MSG_DELSETELEM, он интерпретирует свой аргумент elem неправильно, так что повреждённый nft_set_ext *ext оказывается на несколько байт после правильного местоположения. Это означает, что определённое пользователем поле данных исходного nft_set_ext теперь интерпретируется как заголовки, что приводит к путанице типов.

Один из способов злоупотребить этой путаницей типов — подделать заголовки повреждённого nft_set_ext со значениями смещений таким образом, чтобы nf_tables_set_elem_destroy() интерпретировала содержимое любых смежных блоков памяти как список nft_expr для уничтожения через следующие вызовы:

root@kitploit:~
nft_set_elem_expr_destroy()
    __nft_set_elem_expr_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy()

На этом этапе эксплуатации у нас ещё нет деталей макета памяти ядра. Поэтому невозможно сформировать абсолютные адреса указателей. Однако при создании повреждённых заголовков nft_set_ext мы всё ещё можем использовать смещения вне диапазона, так что expr->ops->destroy() вызывается на определённых валидных nft_expr в смежных блоках памяти.

Для этого мы распыляем выражения nft_log с контролируемым NFTA_LOG_PREFIX. Затем nft_log->prefix освобождается функцией nft_log_destroy(), когда expr->ops->destroy() вызывается:

root@kitploit:~
static void nft_log_destroy(const struct nft_ctx *ctx,
                            const struct nft_expr *expr)
{
        struct nft_log *priv = nft_expr_priv(expr);
        struct nf_loginfo *li = &priv->loginfo;

        if (priv->prefix != nft_log_null_prefix)
                kfree(priv->prefix);

Обратите внимание, что мы всё ещё можем получить доступ к этой памяти и даже снова освободить её через другую ссылку из распылённого выражения nft_log.

Кроме того, мы можем контролировать размер nft_log->prefix, так что он может быть выделен из любого из slab-кэшей kmalloc-{8, ..., 192}. Наконец, упомянутая память интерпретируется ядром как строка символов, поэтому нет необходимости беспокоиться о повреждениях при наложении разных объектов поверх неё. По сути, это конец игры.

Одно неудобство: любые нулевые символы завершают nft_log->prefix, поэтому мы не можем читать дальше нулевых байтов при утечке содержимого памяти. Это решается на следующем шаге, где мы выделяем nft_object->udata для повторного использования фрагмента памяти nft_log->prefix и уничтожаем выражение nft_log. Это освобождает память nft_object->udata, но теперь мы всё ещё можем использовать висящий указатель nft_object->udata для утечки содержимого памяти без ограничений на нулевые байты.

В поисках подходящих структур для следующих шагов мы остановились на nft_expr, выделяемом из nft_dynset_new(). Они находятся в тех же slab-кэшах, что и nft_log->prefix и nft_object->udata. Кроме того, мы имеем разумный контроль над размером выделения, так что позже мы могли бы легко переключаться между slab-кэшами разного размера, если потребуется.

Чтобы использовать эти структуры, мы создаём фильтр пакетов с выражением nft_dynset. И когда мы отправляем любые пакеты через интерфейс loopback, выражение nft_dynset вызывает nft_dynset_new() для создания новых элементов для связанного nft_set. Созданные элементы являются stateful-выражениями следующих типов:

  • nft_counter для получения местоположения модуля nf_tables.ko в памяти ядра.
    Структура включает указатель на nft_counter_ops в модуле ядра nf_tables.ko. Мы раскрываем этот указатель, читая nft_object->udata.

  • nft_quota для произвольного чтения и записи памяти.
    Мы можем многократно освобождать и перераспределять nft_object->udata, чтобы изменить указатель nft_quota->consumed. Затем мы выполняем операцию NFT_MSG_GETSETELEM, которая вызывает nft_quota_do_dump() для чтения содержимого указанной памяти и передаёт результат в атрибуте NFTA_QUOTA_CONSUMED в ответе. Что касается записи, мы просто отправляем пакеты через интерфейс loopback, где nft_quota_do_eval() вызывает:

    root@kitploit:~
      static inline bool nft_overquota(struct nft_quota *priv,
                                       const struct sk_buff *skb)
      {
              return atomic64_add_return(skb->len, priv->consumed) >=
    

Мы используем вышеуказанное произвольное чтение памяти, чтобы получить базовый адрес ядра. Затем мы приступаем к изменению подстроки "sbin" в пути "/sbin/modprobe", чтобы она была заменена на "/tmp". Полученный путь "//tmp/modprobe" затем используется ядром для запуска процесса с правами root, где мы контролируем содержимое файла.

Обратите внимание, что мы не предпринимали намеренных усилий для обхода целостности потока управления (Control-Flow Integrity). Однако для каждого из шагов эксплуатации мы сознательно выбрали наиболее гибкие и наиболее надёжные примитивы. Оказалось, что наш выбор каким-то образом избежал всех примитивов, которые потенциально могли бы быть заблокированы целостностью потока управления. Теперь нам интересно подтвердить тестированием, что полученный эксплойт действительно работает против систем с защитой целостности потока управления.

Скачать инструмент

для изменения nft_quota->consumed.