Как превратить публичный PoC в детект: разбираем эксплуатацию Dirty Frag

На связи Алина Байрамова, аналитик-исследователь угроз кибербезопасности R-Vision.
Появление новой уязвимости с рабочим публичным PoC обычно запускает привычный процесс: специалисты определяют затронутые системы, проверяют наличие обновлений и планируют установку патчей. Но до тех пор, пока инфраструктура не обновлена, необходимо понять, можно ли заметить попытку эксплуатации по доступной телеметрии.
С этим не всегда все очевидно. Описание CVE и публичный PoC позволяют понять, как работает уязвимость, но сами по себе не дают готового сценария обнаружения. Эксплойт может использовать штатные механизмы операционной системы, а отдельные действия, которые он выполняет, встречаются и в легитимной активности.
В этой статье разберем три публичных PoC для Dirty Frag: два варианта эксплуатации CVE-2026-43284 через ESP/XFRM и один для CVE-2026-43500 через RxRPC.
Посмотрим, какие системные вызовы выполняют эксплойты, какие следы остаются в событиях Linux и какие признаки можно использовать для обнаружения эксплуатации. В результате соберем детекты для разных вариантов Dirty Frag и сравним, какие признаки остаются стабильными между реализациями PoC.
Что такое Dirty Frag?
Dirty Frag - общее название для двух независимых уязвимостей локального повышения привилегий в Linux: CVE-2026-43284 в подсистеме xfrm ESP и CVE-2026-43500 в модуле RxRPC. Обе уязвимости позволяют атакующему изменить данные файла, доступного только для чтения, в кэше страниц page cache.
Общий принцип эксплуатации выглядит следующим образом: системный вызов splice() передаёт страницы файла по цепочке файл → pipe → сетевой сокет без копирования в пользовательское пространство. В результате фрагмент сетевого буфера skb может ссылаться непосредственно на страницу исходного файла.
Перед изменением такого фрагмента ядро должно создать отдельную копию данных. Однако в уязвимых путях ESP и RxRPC криптографическая обработка выполняется непосредственно над разделяемой страницей. Поэтому изменение сетевого пакета одновременно изменяет кэшированную копию /etc/passwd или usr/bin/su, или другого читаемого файла.
В CVE-2026-43284 этот путь проходит через обработчик ESP, а в CVE-2026-43500 - через проверку пакета RxRPC.
Подробно останавливаться на внутреннем устройстве уязвимости не будем. Подробное описание внутренних структур ядра и обеих веток эксплуатации приведено в разборе Ideco и оригинальном исследовании Hyunwoo Kim.
Для этой статьи важнее другое: какие действия выполняют конкретные PoC и какие следы они оставляют в телеметрии Linux.
Начнем с первого ESP/XFRM-варианта.
PoC № 1: Copy_Fail2-Electric_Boogaloo - CVE-2026-43284
Первый PoC опубликован в репозитории Copy_Fail2-Electric_Boogaloo. Это компактная реализация эксплуатации CVE-2026-43284 через ESP/XFRM.
Скрипт выбирает в /etc/passwd длинную запись пользователя с оболочкой nologin, false или sync, сохраняет исходную строку в /var/tmp/.cf2.state и формирует запись вида sick::0:0:<padding>:/bin/bash. Затем для каждого отличающегося байта запускается copyfail2 с целевым смещением и нужным значением.
Сам бинарный файл открывает целевой файл только на чтение. Страница попадает в pipe и UDP-сокет через splice(). Перед этим на сокете включается UDP_ENCAP, чтобы переданные данные прошли через ESP-обработчик ядра. После изменения page cache скрипт перечитывает /etc/passwd и выполняет su - sick.
Ключевая последовательность:
socket(AF_INET, SOCK_DGRAM, 0)
→ setsockopt(IPPROTO_UDP, UDP_ENCAP)
→ bind(127.0.0.1:4500)
→ splice(файл → pipe)
→ splice(pipe → UDP-сокет)
→ чтение /etc/passwd
→ su - sick
Таким образом, эксплуатация строится на последовательности:

В тестовой среде PoC запускался непривилегированным пользователем командой: ./run.sh

При проверке, видим что текущий пользователь получил привилегии root.
Анализ событий и способы обнаружения эксплуатации
При запуске ESP-варианта PoC в SIEM фиксируется повторяющаяся последовательность системных вызовов:
socket(AF_INET, SOCK_DGRAM, 0)
→ setsockopt(IPPROTO_UDP, UDP_ENCAP)
→ splice
Последовательность повторяется при обработке отдельных фрагментов полезной нагрузки; ниже приведён один из таких фрагментов. Оранжевым выделены название системного вызова, его аргументы, идентификатор процесса и имя пользователя.

В поле dst_object_name указано название системного вызова, а в dst_object_value — переданные ему аргументы.
Для socket значения a0=2, a1=2 и a2=0 соответствуют AF_INET, SOCK_DGRAM и IPPROTO_IP.
Вызов setsockopt включает UDP-инкапсуляцию. Значения a1=11 и a2=64 соответствуют IPPROTO_UDP и UDP_ENCAP.
Для splice достаточно учитывать успешный вызов с действием copy. Аргументы a0–a3 в правило не включаются, поскольку содержат файловые дескрипторы и указатели на смещения, которые могут различаться между операциями.
По отдельности эти системные вызовы не являются признаком эксплуатации. Для обнаружения важна именно их последовательность в рамках одного процесса.
На основе этих событий можно собрать следующее правило корреляции:
filter: !vrl |
.device_vendor == "linux" &&
.device_product == "auditd" &&
.outcome == "success" &&
.src_user_id != "4294967295" &&
includes(["socket", "setsockopt", "splice"], .dst_object_name)
aliases:
udp_socket:
filter: !vrl |
object_value, _err = downcase(.dst_object_value)
.dst_object_name == "socket" &&
contains_all(object_value, ["a0=2", "a1=2", "a2=0"])
udp_encap:
filter: !vrl |
object_value, _err = downcase(.dst_object_value)
.dst_object_name == "setsockopt" &&
contains_all(object_value, ["a1=11", "a2=64"])
splice_call:
filter: !vrl |
.dst_object_name == "splice" &&
.action == "copy"
select:
alias: udp_socket
join:
alias: udp_encap
on:
- eq: { udp_socket: .device_hostname, udp_encap: .device_hostname }
- eq: { udp_socket: .src_process_id, udp_encap: .src_process_id }
- eq: { udp_socket: .src_user_name, udp_encap: .src_user_name }
join:
alias: splice_call
on:
- eq: { udp_encap: .device_hostname, splice_call: .device_hostname }
- eq: { udp_encap: .src_process_id, splice_call: .src_process_id }
- eq: { udp_encap: .src_user_name, splice_call: .src_user_name }PoC № 2: dirtyfrag - ESP-ветка CVE-2026-43284
Второй репозиторий - V4bel/dirtyfrag. Его exp.c содержит универсальную обвязку и два пути эксплуатации. Сначала разберём ветку ESP.
Здесь целевой объект - /usr/bin/su. PoC собирает небольшой ELF-файл, который после запуска выполняет команды повышения привилегий и запускает /bin/sh. Полезная нагрузка разделяется на фрагменты по четыре байта, а для каждого фрагмента создаётся отдельная XFRM Security Association.
В отличие от Copy_Fail2, заголовок ESP помещается в pipe через vmsplice(). Затем splice() добавляет фрагмент /usr/bin/su и передаёт буфер в UDP-сокет. XFRM SA регистрируется через netlink, а необходимые capability подготавливаются в user и network namespace.
Ключевая последовательность:

Для запуска ESP/XFRM-варианта PoC используется параметр --force-esp:
./exp --force-esp -v
В результате запуска ESP-варианта PoC были изменены первые 192 байта кэшированной копии /usr/bin/su, начиная со смещения 0x0.
Вместо исходных данных PoC помещает туда небольшой ELF-образ, который при запуске устанавливает UID и GID процесса в 0 и запускает root-оболочку.
Анализ событий и способы обнаружения эксплуатации
В ходе запуска ESP-варианта PoC процесс регистрирует параметры XFRM, создаёт UDP-сокеты, включает UDP-инкапсуляцию и передаёт подготовленные данные через splice().
Для обнаружения эксплуатации выделим основную последовательность системных вызовов:
socket(AF_INET, SOCK_DGRAM, 0)→ setsockopt(IPPROTO_UDP, UDP_ENCAP)→ splice()
PoC выполняет эту последовательность многократно. Ниже показаны нормализованные события из R-Vision SIEM. Оранжевым выделены название системного вызова, его аргументы, идентификатор процесса и имя пользователя.

Параметры вызовов socket и setsockopt, а также логика обработки splice совпадают с первым сценарием.
Это важный результат сравнения двух PoC. Несмотря на различия в подготовке полезной нагрузки и внутренней логике, обе реализации CVE-2026-43284 оставляют одинаковый ключевой след на уровне auditd.
Поэтому отдельное правило для второго PoC не требуется. Одна последовательность системных вызовов покрывает оба рассмотренных ESP/XFRM-варианта:
socket(AF_INET, SOCK_DGRAM, 0)→ setsockopt(IPPROTO_UDP, UDP_ENCAP)→ splice()
Для второй уязвимости Dirty Frag эта логика уже не подходит: механизм эксплуатации и вызываемая последовательность системных вызовов отличаются.
PoC № 3: dirtyfrag - CVE-2026-43500, RxRPC
Здесь PoC не использует UDP_ENCAP, поэтому последовательность
socket(AF_INET, SOCK_DGRAM, 0)→ setsockopt(IPPROTO_UDP, UDP_ENCAP)→ splice()
для этого варианта не формируется.
Сначала PoC добавляет в keyring процесса специально сформированный RxRPC-ключ с помощью add_key(). Затем создается сокет семейства AF_RXRPC:
socket(AF_RXRPC, SOCK_DGRAM, PF_INET)
После этого через setsockopt() задаются параметры RxRPC:
setsockopt(fd, SOL_RXRPC, RXRPC_SECURITY_KEY, ...)→ setsockopt(fd, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL, ...)
Далее PoC запускает локальный RxRPC-сеанс и формирует служебные пакеты для прохождения проверки rxkad.
Заголовок вредоносного пакета помещается в pipe через vmsplice(), а содержимое целевого файла передается туда с помощью splice().

Таким образом, в этой цепочке могут встречаться два разных типа сокетов: обычный UDP-сокет, через который передаётся сформированный пакет, и сокет AF_RXRPC, на котором пакет обрабатывается RxRPC-подсистемой. Именно наличие AF_RXRPC помогает отличить данный сценарий от ESP/XFRM-варианта.
При обработке пакета функция rxkad_verify_packet_1() расшифровывает первые 8 байт полезной нагрузки непосредственно в исходном буфере. Если этот буфер связан со страницей page cache, результат расшифрования записывается в ту же страницу памяти.
PoC заранее подбирает ключ и содержимое токена, чтобы результат расшифрования дал требуемые байты. Затем изменённые данные могут использоваться для подмены записи в /etc/passwd и получения root-доступа. Подробная последовательность вызовов приведена в исходном коде PoC.
Для отдельного тестирования RxRPC-ветки PoC запускается командой:
./exp --force-rxrpcВ результате выполнения PoC запись пользователя root в кэшированной копии файла /etc/passwd изменяется: поле пароля становится пустым. После этого атакующий получает root-оболочку.

Анализ событий и способы обнаружения эксплуатации
В RxRPC-варианте отсутствует вызов setsockopt(IPPROTO_UDP, UDP_ENCAP). Основная последовательность для обнаружения эксплуатации выглядит так:
setsockopt(SOL_RXRPC, RXRPC_SECURITY_KEY)→ setsockopt(SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL)→ splice()
Первые два вызова настраивают параметры безопасности RxRPC, после чего PoC передаёт подготовленные данные через pipe с помощью splice(). На скриншоте показан фрагмент этой последовательности. Оранжевым выделены идентификатор процесса, имя пользователя, названия системных вызовов и значимые аргументы.

Все события были выполнены одним процессом от имени одного пользователя, поэтому для корреляции также можно использовать имя хоста, идентификатор процесса и имя пользователя.
В первом вызове setsockopt (syscall=54) значение a1=110 обозначает уровень SOL_RXRPC, а a2=1 — параметр RXRPC_SECURITY_KEY. Значение a0 является файловым дескриптором сокета, а a3 — указателем на структуру параметров.
Во втором вызове setsockopt сохраняется тот же уровень a1=110, но a2=4 соответствует параметру RXRPC_MIN_SECURITY_LEVEL.
Для splice (syscall=275) учитывается успешный вызов с действием copy. Его аргументы не фиксируются: a0 и a2 обозначают файловые дескрипторы, а a1 и a3 — указатели на смещения, поэтому могут различаться между операциями.
Две последовательные настройки setsockopt с уровнем SOL_RXRPC являются более специфичным признаком, чем обычный вызов setsockopt, поскольку указывают на подготовку RxRPC-сеанса.
В ходе одного запуска PoC может появляться несколько вызовов splice() с разными файловыми дескрипторами. Это связано с тем, что программа отдельно помещает заголовок в pipe, переносит в pipe данные целевого файла, а затем передаёт объединённый буфер в UDP-сокет.
По описанным событиям можно составить правило корреляции, в котором события связываются по имени хоста, идентификатору процесса и имени пользователя. Это позволяет отличить последовательность действий PoC от отдельных легитимных вызовов setsockopt или splice.
Ниже приведён пример корреляционного фильтра на языке VRL:
filter: !vrl |
.device_vendor == "linux" &&
.device_product == "auditd" &&
.device_event_id == "1300" &&
.device_action == "syscall" &&
.outcome == "success" &&
includes(["setsockopt", "splice"], .dst_object_name)
aliases:
rxrpc_security_key:
filter: !vrl |
dst_object_value, _err = downcase(.dst_object_value)
.dst_object_name == "setsockopt" &&
contains_all(dst_object_value, ["a1=110,", "a2=1,"])
rxrpc_min_security:
filter: !vrl |
dst_object_value, _err = downcase(.dst_object_value)
.dst_object_name == "setsockopt" &&
contains_all(dst_object_value, ["a1=110,", "a2=4,"])
splice_call:
filter: !vrl |
.dst_object_name == "splice" &&
.action == "copy"
select:
alias: rxrpc_security_key
join:
alias: rxrpc_min_security
on:
- eq: { rxrpc_security_key: .device_hostname, rxrpc_min_security: .device_hostname }
- eq: { rxrpc_security_key: .src_process_id, rxrpc_min_security: .src_process_id }
- eq: { rxrpc_security_key: .src_user_name, rxrpc_min_security: .src_user_name }
join:
alias: splice_call
on:
- eq: { rxrpc_min_security: .device_hostname, splice_call: .device_hostname }
- eq: { rxrpc_min_security: .src_process_id, splice_call: .src_process_id }
- eq: { rxrpc_min_security: .src_user_name, splice_call: .src_user_name }Настройка auditd
Чтобы отслеживать системные вызовы, используемые PoC, и передавать их в коррелятор SIEM, необходимо предварительно настроить auditd.
Приведённые ниже правила рассчитаны на 64-битную архитектуру x86_64. В сырых audit-событиях числовые аргументы обычно отображаются в шестнадцатеричном виде без префикса 0x. Например, a1=110 соответствует 0x110, то есть 272 в десятичной системе.
# x86_64, IPv4 UDP socket
-a always,exit -F arch=b64 -S socket -F a0=0x2 -F a1=0x2 -F a2=0x0 -F success=1 -F key=dirtyfrag_socket_v4
# x86_64, IPv6 UDP socket
-a always,exit -F arch=b64 -S socket -F a0=0xa -F a1=0x2 -F a2=0x0 -F success=1 -F key=dirtyfrag_socket_v6
# IPPROTO_UDP = 0x11, UDP_ENCAP = 0x64
-a always,exit -F arch=b64 -S setsockopt -F a1=0x11 -F a2=0x64 -F success=1 -F key=dirtyfrag_encap
-a always,exit -F arch=b64 -S splice -F auid>=1000 -F auid!=-1 -F success=1 -F key=dirtyfrag_splice
# SOL_RXRPC = 0x110 = 272
-a always,exit -F arch=b64 -S setsockopt -F a1=272 -F a2=1 -F success=1 -F key=dirtyfrag_rxrpc_security_key
Эти правила позволяют отдельно фиксировать ключевые вызовы, которые используются в рассмотренных PoC: создание UDP-сокета, настройку UDP_ENCAP, вызов splice, а также параметры setsockopt, характерные для RxRPC-варианта эксплуатации.
Какие системы могут быть уязвимы?
Уязвимость в xfrm-ESP присутствует в ядре Linux с января 2017 года, в RxRPC — с июня 2023 года. Эксплоит, подготовленный исследователем, работает в нескольких крупных дистрибутивах, комбинируя обе уязвимости.
Подтверждена работа:
Ubuntu 24.04.4 с ядром
6.17.0-23RHEL 10.1 с ядром
6.12.0-124.49.1openSUSE Tumbleweed с ядром
7.0.2-1CentOS Stream 10 с ядром
6.12.0-224AlmaLinux 10 с ядром
6.12.0-124.52.3Fedora 44 с ядром
6.19.14-300
Кроме того, успешность эксплуатации зависит от конфигурации системы:
разрешено создание user namespace;
модули
esp4,esp6иrxrpcзагружены либо доступны для загрузки;разрешена загрузка этих модулей;
AppArmor или SELinux не блокируют необходимые операции;
используется уязвимая версия ядра.
Меры защиты
Основная мера защиты — обновить ядро до пакета дистрибутива, содержащего исправления для обеих веток Dirty Frag:
исправление ESP/XFRM — upstream-коммит
f4c50a4;исправление RxRPC — upstream-коммит
aa54b1d.
Если обновление временно невозможно, можно рассмотреть блокировку загрузки уязвимых модулей. Делать это следует только после проверки роли узла и зависимости сервисов от IPsec, ESP, AFS и RxRPC.
sudo sh -c 'printf "install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n" > /etc/modprobe.d/dirtyfrag.conf'
sudo modprobe -r esp4 esp6 rxrpc
Если эксплуатация уже произошла, сначала необходимо изолировать узел и сохранить доступные артефакты. После этого можно сбросить page cache:
echo 3 > /proc/sys/vm/drop_cachesВывод
Сетевые и криптографические механизмы Linux обычно воспринимаются как фоновая системная активность. Однако Dirty Frag показывает, что они могут стать частью цепочки локального повышения привилегий. Используя штатные механизмы splice(), сетевые сокеты и обработку пакетов, атакующий способен изменить страницу читаемого файла в page cache без обычного write(). Для CVE-2026-43284 этот путь проходит через xfrm/ESP, а для CVE-2026-43500 — через RxRPC.
Разбор публичных PoC позволяет выделить практические признаки такой активности. Для ESP/XFRM это последовательность:
socket→ setsockopt(UDP_ENCAP)→ splice()
Для RxRPC:
AF_RXRPC→ add_key→ setsockopt→ splice→ recvmsg
Эти события следует коррелировать по одному хосту и процессу в коротком временном окне.
Предложенные правила позволяют обнаружить попытку пройти уязвимым путём, но не подтверждают успешное повышение привилегий. Для этого нужны дополнительные признаки: запуск процесса с UID 0, выполнение su или другого setuid-бинарника, проверка целевого файла после очистки page cache или перезагрузки, а также анализ версии и конфигурации ядра.
Основной мерой защиты остаётся установка исправленного ядра. Если обновление временно невозможно, можно ограничить загрузку модулей esp4, esp6 и rxrpc, предварительно оценив влияние на IPsec, AFS и другие сервисы.
Источники
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.