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

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

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

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

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

Категории

Все категории
Loading categories
TEST-CVE-2023-32233 — CVE-2023-32233 | Kitploit
Инструменты/GitHubGitHub/rogeliopumajulca/test-cve-2023-32233
Криминалистика памятиАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubrogeliopumajulca/test-cve-2023-32233

TEST-CVE-2023-32233

CVE-2023-32233

Репозиторий
2 лет назадЕщё не проверено

Популярное

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

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

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

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

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

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

Детали уязвимости

Затронутый код происходит из официального ядра 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() that points to nft_lookup_destroy()
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() that deallocates memory used by `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. Созданные элементы — это выражения с состоянием следующих типов:

  • 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.