Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
page_inject — CVE-2026-31431-killed 页面缓存漏洞利用 — 共享同一镜像层的容器中的代码执行 | Kitploit
工具/GitHubGitHub/sgkdev/page_inject
漏洞分析漏洞利用后渗透利用渗透测试红队容器逃逸二进制利用
GitHubsgkdev/page_inject

page_inject

CVE-2026-31431-killed 页面缓存漏洞利用 — 共享同一镜像层的容器中的代码执行

查看仓库
74143个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

page_inject - AF_ALG aead 跨容器逃逸

AF_ALG aead 漏洞跨容器利用——从一个被攻陷的容器横向移动到每一个共享同一 libc.so.6 镜像层的兄弟容器。

这是一个逃逸原语:它运行在攻击者已经攻陷的未授权容器内部,并利用 AF_ALG authencesn ESN 旋转 4 字节任意写入漏洞(CVE-2026-31431)在 libc.so.6 的页缓存页中植入一个持久的 read() 钩子。由于 Docker/containerd 使用共享 inode 支持 overlayfs 下层文件,这些页面对从同一镜像实例化的每个兄弟容器都可见——钩子也会在它们的进程中触发,攻击者从而在每个容器内获得命令执行能力。

威胁模型

  • 攻击者对主机上的单个容器(称为 victim)拥有 Shell 访问权,该主机还运行着与 victim 使用同一镜像的其他容器(siblings)。
  • victim 使用默认 Docker/k8s 配置运行:容器用户命名空间内的非特权 UID、默认 seccomp 配置文件、默认 AppArmor 配置文件、无特殊权限、无主机绑定挂载。
  • 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_*,无需主机文件系统访问。攻击者将一个自包含的静态链接二进制文件放入容器内运行,页缓存损坏——进而钩子——对每个兄弟容器都可见。

利用链如何组合

  1. 页缓存页身份。 在 overlayfs 容器内,/usr/lib/.../libc.so.6 由下层镜像层的 ext4 inode 提供服务。从同一镜像启动的每个容器共享该后端 inode,并且内核的页缓存由底层 inode 键控——而非由 overlay 或命名空间决定。因此,对页缓存页的一次 4 字节写入对所有具有该页 mmap 的兄弟容器进程都可见。

  2. AF_ALG aead 漏洞将一次写入转变为多次。 algif_aead 将用户 RX iovec 与拼接后的 TX SGL 末尾的 authsize 字节连接起来,而 authencesn 的 ESN 旋转将 AAD 的 seq_high 字段的 4 字节放置在 dst[assoclen + cryptlen] 处——即该连接的外部尾部的第一个字节。拼接页面是攻击者仅具有读取权限的文件的页缓存页,但密码操作仍将字节复制到其中,且不进行脏记账。(参见 crypto/algif_aead.c 和 crypto/authencesn.c 了解底层机制。)

  3. 引导可调用的原语。 page_inject 做的第一件事是引导区域 A——一个汇编编码的、相同 AF_ALG 操作(write_cache.asm)的重实现,放置在 libc 的 .text 空洞内。这使得 4 字节写入成为未来任何钩子负载中的常规 call,无需每次调用都进行 Socket 设置。

构建

注入器在受害容器外部构建——通常是在攻击者自己的开发机器上——因为大多数生产容器镜像不包含编译器。需要具备 gcc(支持 -static 链接)和 nasm 的标准 Linux x86_64 开发环境。

root@kitploit:~
make            # 通过 gen_arrays.sh 汇编 .asm 源文件,链接静态 page_inject
make shellcode  # 同时生成可供检查的 .bin 平面二进制文件
make clean      # 删除生成的文件和二进制文件

输出是一个单独的静态链接 ELF(./page_inject),可在任何现代 x86_64 Linux 内核上运行。

交付和使用(在受害容器内)

一旦攻击者在 victim 上获得 Shell,他们将二进制文件上传到可写目录(通常为 /tmp):

root@kitploit:~
# 在已攻陷的容器内部,攻击者会话
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

# 自动检测,不限发行版:
victim$ ./page_inject --root /

上述任一调用效果相同:ELF 解析容器内的 libc,在其页缓存中安装钩子,在大约 30 秒内监视槽表以等待兄弟容器注册,并对第一个注册的兄弟容器执行一次性的 id 作为完备性检查。

引导完成后,可进入命令 Shell 以驱动任何已注册的兄弟容器:

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>   使用内置固定路径查找表,在 <prefix> 下自动解析 libc.so.6。
                    在受害容器内通常为 --root / 。
  --shell [0xKEY]   注入后进入交互式命令 Shell。可选 KEY 预选目标。
  --no-bootstrap    跳过注入(仅 Shell;钩子必须已在页缓存中生效)。
  --timeout SEC     --shell 模式下的槽监视超时(默认 30 秒)。
  --help, -h        显示帮助。

默认 libc(当未指定 --root 且未提供 LIBC_PATH 时):
  /usr/lib/x86_64-linux-gnu/libc.so.6

双注入路径

不同的 glibc 构建在可执行 LOAD 段与下一个只读 LOAD 段之间留下不同大小的 .text 空洞空间。page_inject 在注入时选择两种布局之一:

  • 路径 A——仅 libc(默认)。 区域 C 和区域 A 均位于 libc 的 .text 空洞内。槽表 + CMD + OUTPUT 区域位于 libc 的 .hash 节中——这是古老的 SysV 哈希数据,ld.so 在运行时不再读取它,因为它使用 .gnu.hash。当 .hash 不存在时(Arch 的现代工具链),page_inject 改为从 .eh_frame_hdr 的尾部切割出槽区域,首先缩小 fde_count 字段,以便展开器不再将释放的字节视为 FDE 二叉搜索索引的一部分(展开器会透明地退化为对 .eh_frame 的线性扫描,以查找其 FDE 曾在截断范围内的任何 IP——LSB 规定行为)。

  • 路径 B——libc 跳板 + ld.so 负载。 某些 glibc 构建将 libc 空洞缩小到低于完整区域 C + 区域 A 负载所需的大小(Ubuntu 24.04 / glibc 2.39 仅有 711 字节空洞)。在这种情况下,page_inject 将一个 36 字节的跳板写入 libc 的空洞——它执行 libc 内部的快速路径 .bss 键门控——并在慢速路径上,根据 libc 中 _rtld_global 的 GOT 槽(每个 glibc 私有导入的 ld.so 端符号)计算 ld.so 的运行时基址,然后跳转到 ld.so 的 .text 空洞中的一个基址寄存器变体区域 C。槽表 + CMD + OUTPUT + .bss 键保留在 libc 中;ld.so 端的区域 C 通过跳板设置的 后的 到达它们。

如果两种布局都不适合,page_inject 会干净地拒绝,不会向磁盘或页缓存中的 libc 或 ld.so 写入任何内容。

read() 序言处理

不同 glibc 版本在 read() 中发出不同的起始序列。注入器识别每一种,读取钩子所替换的字节,并在区域 C 的快速路径中模拟它们,以便单线程的 read() 在 read+N 处正确恢复:

区域 C 中的快速路径模拟槽大小为已知最长序言(8 字节)加上 5 字节的 rel32 跳转;较短的序言用 NOP 填充剩余槽字节,使槽总长度恒定。

文件布局

root@kitploit:~
page_inject/
  page_inject.c           主注入器:ELF 解析,漏洞原语,双路径布局选择,注入 + 取消挂钩。
  zone_c.asm              路径 A 钩子分发器 Shellcode。
  zone_c_ld.asm           路径 B 钩子分发器(rbp 基址变体)。
  trampoline.asm          路径 B 36 字节 libc 端存根。
  write_cache.asm         区域 A(漏洞写入原语 Shellcode)。
  gen_arrays.sh           汇编 .asm -> asm_bytecode.c。
  asm_bytecode.c          [生成] Shellcode 字节数组。
  Makefile                构建系统。

测试和支持矩阵

该漏洞已在以下容器快照发行版上进行了端到端验证。每个条目均已从一个容器内部注入 page_inject,并看到钩子在从同一镜像启动的兄弟容器中触发;通过页缓存通道正确执行命令;取消挂钩干净地恢复了 libc 页面状态。

操作说明

  • page_inject 故意是静态链接的,以便攻击者自身的进程不受所安装钩子的影响。
  • page_inject 能识别“已挂钩”的 libc(在 read() 序言处为 E9 + NOP),并拒绝重新注入。如果您在测试环境中且页缓存卡在该状态,请停止所有使用该镜像的容器并执行 drop_caches 以重置。
下载工具
  • 安装钩子。 然后注入器将区域 C(zone_c.asm)写入 libc 的 .text 空洞,并将 read() 的前 7-12 个字节修补为跳转到该处的 E9 disp32。序言中被替换的字节在区域 C 的快速路径中被忠实地模拟(识别出三种不同的 glibc 序言——见下方的“read() 序言处理”)。钩子现在在 libc 的页缓存中生效。

  • 钩子传播。 每个兄弟容器都有持续调用 read() 的进程(日志守护进程、健康检查、cat /etc/hostname,任何操作)。在兄弟容器内的首次此类调用中,被劫持的序言跳转到区域 C,区域 C 执行以下操作:

    • stat("/") 获取容器的根 inode(每个命名空间稳定的 ID),将其用作容器的槽键,
    • 扫描槽表以查找具有该键的现有条目,
    • 如果不存在,则注册该键并 fork() 出一个长期运行的命令循环子进程,该子进程轮询 CMD 区域以获取命令,
    • 返回 read()+N,使调用者毫无察觉。 原始兄弟进程继续运行。从现在开始,攻击者在该容器内拥有一个守护进程。
  • 命令通道。 攻击者使用相同的 page_inject 二进制文件以 --shell 模式将命令写入槽区域的 CMD 区域。每个注册的兄弟容器钩子子进程轮询,fork 出一个 /bin/sh -c <cmd>,将 stdout/stderr 捕获到 OUTPUT 区域,发出完成信号,然后继续轮询。Shell 显示输出。由于每个 CMD/OUTPUT 写入同样通过漏洞原语进行,因此无需特殊权限。

  • 取消挂钩。 完成后,unhook 恢复 read() 的原始序言字节并将槽表清零;钩子子进程在下一次迭代中看到空槽并自行终止。页缓存修改本身是干净的(内核从未将修改后的页面标记为脏),因此一旦每个将 libc 映射到内存的容器停止,drop_caches 即可完全还原缓存——磁盘上不留任何痕迹。

  • rbp = libc_base
    rbp + 偏移
    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 相对,因此字节复制是忠实的)。
    镜像glibc注入路径Read() 序言槽区域
    debian:bookworm2.36Acmpb.hash
    ubuntu:24.042.39Bcmpb.hash(libc 端,通过来自 ld.so 的 rbp 寻址)
    ubuntu:22.042.35ATLS-fs.hash
    fedora:402.39Acmpb.hash
    archlinux:latest2.43Apush-rbp.eh_frame_hdr(截断尾部)