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

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

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

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

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

Категории

Все категории
Loading categories
page_inject — CVE-2026-31431-killed page-cache эксплойт — выполнение кода в контейнерах, совместно использующих один и тот же слой образа | Kitploit
Инструменты/GitHubGitHub/sgkdev/page_inject
Анализ уязвимостейЭксплуатацияПост-эксплуатацияТестирование на ПроникновениеRed TeamingПобег из КонтейнераЭксплуатация Бинарных Файлов
GitHubsgkdev/page_inject

page_inject

CVE-2026-31431-killed page-cache эксплойт — выполнение кода в контейнерах, совместно использующих один и тот же слой образа

Репозиторий
741464 месяцев назадПроверено Kitploit

Популярное

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

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

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

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

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

page_inject — межконтейнерный эскейп через AF_ALG aead

Межконтейнерный эксплойт уязвимости AF_ALG aead — переход из одного скомпрометированного контейнера во все соседние контейнеры, использующие тот же слой образа libc.so.6.

Это примитив эскейпа: он запускается изнутри непривилегированного контейнера, который атакующий уже скомпрометировал, и использует 4-байтовую ошибку произвольной записи при ротации ESN в authencesn из AF_ALG (CVE-2026-31431), чтобы внедрить постоянный хук read() в страницы page-cache файла libc.so.6. Поскольку в Docker / containerd файлы нижних слоёв overlayfs опираются на общие inode, эти страницы видны каждому соседнему контейнеру, созданному из того же образа: хук срабатывает и в их процессах, и атакующий получает выполнение команд в каждом из них.

Модель угроз

  • Атакующий имеет доступ к shell в одном контейнере (назовём его victim) на хосте, где работают другие контейнеры (siblings) из , что и .
того же образа
victim
  • victim работает в стандартной конфигурации Docker/k8s: непривилегированный uid внутри пользовательского пространства имён контейнера, профиль seccomp по умолчанию, профиль AppArmor по умолчанию, без специальных capabilities, без bind-монтирований хоста.
  • victim имеет только:
    • доступ на чтение к своей libc (/usr/lib/x86_64-linux-gnu/libc.so.6 или туда, куда её устанавливает дистрибутив)
    • стандартное семейство системных вызовов socket(AF_ALG, ...)
    • стандартные системные вызовы splice / vmsplice
    • доступ на запись в каталог, к которому можно применить chmod +x (например, /tmp)
  • Ядро должно быть уязвимо к CVE-2026-31431 (любая сборка algif_aead + authencesn до исправления-отката в апстриме).
  • На этом всё. Никаких специальных CAP_*, никакого доступа к файловой системе хоста. Атакующий помещает самодостаточный статически слинкованный бинарник в контейнер, запускает его — и повреждение page-cache, а следовательно и хук, становится видимым для всех соседних контейнеров.

    Как эксплойт собирается в цепочку

    1. Идентичность страниц page-cache. В контейнере на overlayfs путь /usr/lib/.../libc.so.6 обслуживается ext4-inode нижнего слоя образа. Каждый контейнер, запущенный из того же образа, разделяет этот нижележащий inode, а page-cache ядра индексируется по нижележащему inode — не по overlay и не по пространству имён. Поэтому одна 4-байтовая запись в страницу page-cache видна всем процессам соседних контейнеров, у которых эта страница отображена через mmap.

    2. Уязвимость AF_ALG aead превращает одну такую запись во множество. algif_aead сцепляет пользовательский RX iovec с хвостовыми authsize байтами сцепленного TX SGL, а при ротации ESN в authencesn 4 байта поля seq_high из AAD размещаются в dst[assoclen + cryptlen] — то есть в первом байте этого сцепленного чужого хвоста. Сцепленная страница — это страница page-cache файла, к которому атакующий имеет только доступ на чтение, но шифр всё равно копирует в неё байты, не помечая страницы как dirty. (Подробнее о механике — в crypto/algif_aead.c и crypto/authencesn.c.)

    3. Инициализация вызываемого примитива. Первым делом page_inject разворачивает зону A — закодированную на asm повторную реализацию той же схемы AF_ALG (write_cache.asm), размещённую в кармане секции .text libc. Это превращает 4-байтовую запись в обычный call из любого будущего кода хука — без необходимости настраивать сокет для каждого вызова.

    4. Установка хука. Затем инжектор записывает зону C (zone_c.asm) в карман секции .text libc и патчит первые 7–12 байт read() переходом E9 disp32 на неё. Вытесняемые байты пролога точно эмулируются в быстром пути зоны C (распознаются три разных пролога glibc — см. «Обработка пролога read()» ниже). Теперь хук активен в page-cache libc.

    5. Распространение хука. В каждом соседнем контейнере процессы постоянно вызывают read() (демоны журналирования, healthcheck-скрипты, cat /etc/hostname — что угодно). При первом таком вызове внутри соседнего контейнера перехваченный пролог прыгает в зону C, которая:

      • выполняет stat("/") для корневого inode контейнера (стабильный идентификатор для каждого пространства имён) и использует его как ключ слота контейнера;
      • сканирует таблицу слотов в поисках существующей записи с этим ключом;
      • если записи нет, регистрирует ключ и fork()-ает долгоживущего дочернего процесса командного цикла, который опрашивает область CMD на предмет команд;
      • возвращается к read()+N, так что вызывающий код ничего не замечает. Исходный процесс соседнего контейнера продолжает работать. С этого момента у атакующего есть демон внутри этого контейнера.
    6. Канал команд. Атакующий использует тот же бинарник page_inject в режиме --shell, чтобы записывать команды в область CMD региона слотов. Дочерний процесс хука каждого зарегистрированного соседнего контейнера опрашивает её, запускает /bin/sh -c <cmd>, захватывает stdout/stderr в область OUTPUT, сигнализирует о завершении и возвращается к опросу. Shell показывает вывод. Поскольку каждая запись в CMD/OUTPUT тоже идёт через примитив уязвимости, никаких особых привилегий не требуется.

    7. Снятие хука. По завершении работы unhook восстанавливает исходные байты пролога read() и обнуляет таблицу слотов; дочерние процессы хуков при следующей итерации видят пустой слот и завершаются сами. Сами изменения page-cache чистые (ядро никогда не помечало изменённые страницы как dirty), поэтому как только все контейнеры с отображённой через mmap libc остановлены, drop_caches полностью восстанавливает кэш — на диске не остаётся никаких следов.

    Сборка

    Инжектор собирается вне контейнера-жертвы — как правило, на собственной машине разработки атакующего, — потому что в большинстве продакшен-образов контейнеров нет компилятора. Достаточно стандартного окружения разработки Linux x86_64 с gcc (с поддержкой статической линковки -static) и nasm.

    root@kitploit:~
    make            # assembles .asm sources via gen_arrays.sh, links static page_inject
    make shellcode  # also produces inspectable .bin flat binaries
    make clean      # removes generated files and the binary
    

    Результат сборки — единый статически слинкованный ELF (./page_inject), который запускается на любом современном ядре Linux x86_64.

    Доставка и использование (из контейнера-жертвы)

    Получив shell в victim, атакующий загружает бинарник в доступный для записи каталог (обычно /tmp):

    root@kitploit:~
    # inside the compromised container, attacker session
    victim$ ./page_inject
    

    Без аргументов page_inject использует путь по умолчанию /usr/lib/x86_64-linux-gnu/libc.so.6 (расположение Debian/Ubuntu после объединения каталогов). В других дистрибутивах libc лежит по другому пути; можно либо передать его явно, либо использовать --root /, чтобы просканировать встроенную таблицу путей от корня контейнера:

    root@kitploit:~
    # Fedora / Rocky / CentOS
    victim$ ./page_inject /usr/lib64/libc.so.6
    
    # Arch
    victim$ ./page_inject /usr/lib/libc.so.6
    
    # Auto-detect, regardless of distro:
    victim$ ./page_inject --root /
    

    Оба варианта запуска делают одно и то же: разбирают ELF внутриконтейнерной libc, устанавливают хук в её page-cache, ~30 секунд наблюдают за таблицей слотов, пока регистрируются соседние контейнеры, и в качестве проверки работоспособности однократно выполняют id в первом зарегистрировавшемся соседнем контейнере.

    После начальной установки можно войти в интерактивную командную оболочку и управлять любым зарегистрированным соседним контейнером:

    root@kitploit:~
    victim$ ./page_inject --shell --no-bootstrap
    === page-cache shell ===
    Containers (3):
      [0] 0x0018598d  <- target
      [1] 0x001859ab
      [2] 0x001859cd
    
    inject:0018598d> exec id
    uid=0(root) gid=0(root) groups=0(root)
    inject:0018598d> target 0x001859ab
    inject:001859ab> exec hostname
    1ccd66abee9d
    inject:001859ab> exec cat /etc/shadow
    root:$6$.....
    inject:001859ab> unhook
    ... read() prologue restored, slot table zeroed ...
    

    unhook одним действием убирает хук из всех соседних контейнеров и позволяет дочерним процессам хука завершиться самостоятельно.

    root@kitploit:~
    Usage: page_inject [OPTIONS] [LIBC_PATH]
    
    Options:
      --root <prefix>   Auto-resolve libc.so.6 under <prefix> using the
                        built-in fixed-path lookup table. Inside the
                        victim container that's normally --root / .
      --shell [0xKEY]   Drop into interactive command shell after
                        injection. Optional KEY pre-selects the target.
      --no-bootstrap    Skip injection (shell-only; hook must already
                        be live in the page cache).
      --timeout SEC     Slot monitoring timeout in --shell mode
                        (default 30 s).
      --help, -h        Show help.
    
    Default libc (when no --root and no LIBC_PATH given):
      /usr/lib/x86_64-linux-gnu/libc.so.6
    

    Два пути инжекции

    В разных сборках glibc между исполняемым LOAD-сегментом и следующим LOAD-сегментом, доступным только на чтение, остаётся разный объём кармана в .text. page_inject в момент инжекции выбирает одну из двух компоновок:

    • Путь A — только libc (по умолчанию). И зона C, и зона A располагаются в кармане .text libc. Таблица слотов + области CMD + OUTPUT располагаются в секции .hash libc — это унаследованные SysV-хеш-данные, которые ld.so во время выполнения не читает, поскольку вместо них использует .gnu.hash. Когда .hash отсутствует (современный тулчейн Arch), page_inject вместо этого вырезает область слотов из хвоста .eh_frame_hdr, предварительно уменьшив поле fde_count, чтобы unwinder больше не считал освобождённые байты частью индекса бинарного поиска FDE (для любого IP, чей FDE ранее находился в усечённом диапазоне, unwinder прозрачно переходит к линейному сканированию .eh_frame — поведение, предписанное стандартом LSB).

    • Путь B — трамплин в libc + payload в ld.so. Некоторые сборки glibc уменьшают карман libc до размера меньше необходимого для полного payload зоны C + зоны A (Ubuntu 24.04 / glibc 2.39 поставляется с карманом в 711 байт). В этом случае page_inject записывает в карман libc 36-байтовый трамплин: он выполняет проверку .bss-ключа в быстром пути внутри libc, а в медленном пути вычисляет базовый адрес ld.so во время выполнения по GOT-слоту libc для _rtld_global (символ на стороне ld.so, который каждая glibc импортирует приватно) и прыгает в вариант зоны C с базовым регистром, расположенный в кармане .text ld.so. Таблица слотов + CMD + OUTPUT + ключ .bss остаются в libc; зона C на стороне ld.so обращается к ним через rbp + offset, после того как трамплин инициализирует rbp = libc_base.

    Если не подходит ни одна компоновка, page_inject чисто отказывается от работы, ничего не записывая в libc или ld.so — ни на диск, ни в page-cache.

    Обработка пролога read()

    Разные версии glibc порождают разные начальные последовательности в read(). Инжектор распознаёт каждую из них, считывает байты, которые вытесняет хук, и эмулирует их в быстром пути зоны C, чтобы однопоточный вызов read() корректно продолжался с read()+N:

    Диапазон glibcПролог (после опционального endbr64)Примечания
    2.36 / 2.39cmpb $0x0, __libc_single_threaded(%rip)7 байт; эмулируемый cmpb выставляет ZF для исходного jne .Lthreaded.
    2.43push rbp; movsxd rdi,edi; xor r9d,r9d7 байт; эмулируется побайтово.
    2.31 / 2.35mov eax, fs:[0x18]8 байт; эмулируется побайтово (обращение fs:[disp32] абсолютное, а не RIP-относительное, поэтому побайтовое копирование точно).

    Слот эмуляции в быстром пути зоны C рассчитан на самый длинный из известных прологов (8 байт) плюс 5-байтовый rel32-переход; более короткие прологи заполняют последний байт слота NOP-заполнителем, чтобы общая длина слота оставалась постоянной.

    Структура файлов

    root@kitploit:~
    page_inject/
      page_inject.c           Main injector: ELF parsing, vuln primitive,
                              dual-path layout selection, inject + unhook.
      zone_c.asm              Path-A hook dispatcher shellcode.
      zone_c_ld.asm           Path-B hook dispatcher (rbp-base variant).
      trampoline.asm          Path-B 36-byte libc-side stub.
      write_cache.asm         Zone A (vuln write primitive shellcode).
      gen_arrays.sh           Assemble .asm -> asm_bytecode.c.
      asm_bytecode.c          [generated] shellcode byte arrays.
      Makefile                Build system.
    

    Матрица протестированных и поддерживаемых конфигураций

    Эксплойт был проверен по всей цепочке (end-to-end) на следующих дистрибутивах, упакованных в снапшоты контейнеров. Для каждой записи page_inject внедрялся изнутри одного контейнера, и его хук срабатывал в соседнем контейнере, запущенном из того же образа; команды корректно выполнялись через канал page-cache; а unhook чисто восстанавливал состояние страниц libc.

    ОбразglibcПуть инжекцииПролог read()Область слотов
    debian:bookworm2.36Acmpb.hash
    ubuntu:24.042.39Bcmpb.hash (на стороне libc, адресация через rbp из ld.so)
    ubuntu:22.042.35ATLS-fs.hash
    fedora:402.39Acmpb.hash
    archlinux:latest2.43Apush-rbp.eh_frame_hdr (усечённый хвост)

    Эксплуатационные заметки

    • page_inject намеренно слинкован статически, чтобы собственный процесс атакующего не затрагивался установленным им хуком.
    • page_inject распознаёт уже «захваченную» libc (E9 + NOP в прологе read()) и отказывается от повторной инжекции. Если вы находитесь в тестовом окружении и ваш page-cache застрял в таком состоянии, остановите все контейнеры, использующие этот образ, и выполните drop_caches для сброса.
    Скачать инструмент