针对 CVE-2026-98432 的单目标探测工具,该漏洞是 Linux 内核 IPv4 TCP 释放后使用(use-after-free)漏洞,已于 2026 年 10 月修复。
它构造一个携带 IP 选项的 IPv4 SYN 包(默认 24 个 NOP 字节,将 IHL 提升至 11),将其发送到单个主机,并通过头部偏移而非模式匹配来读取响应。数据包逻辑也可离线运行:试运行会打印并重新解析构造的数据包,十六进制分析器可解析捕获的流量,测试套件既不需要 root 权限也不需要网络。
没有目标列表处理,没有 Shodan 集成,也没有批处理模式。每次运行只处理一个地址,除非传入 --allow-public,否则 CLI 会拒绝实验室范围之外的任何地址。该工具假定你拥有目标的所有权或已获得书面测试许可。
python3 -m pip install -e ".[test]"
Python 3.9 或更高版本。仅使用标准库。
试运行,无需特权,不发送数据包:
synipopt --dry-run --target 192.0.2.10
从原始脚本的日志中解析数据包,即它报告为 SYN-ACK 的那个:
synipopt --analyze-hex "$(cat docs/fixtures/original-log-packet.hex)"
探测实验室虚拟机,并将携带选项的 SYN 与普通 SYN 进行对比:
sudo synipopt --target 192.168.56.101 --dport 443 --retries 3 --compare --log probe.jsonl
默认构造,线上 64 字节:
0x01 NOP 填充携带选项的 SYN 正是将 ireq->ireq_opt 放到请求套接字上的原因。内核会编译选项块,而纯 NOP 填充会编译为空块,因此对默认构造的 SYN-ACK 返回时不带 IP 选项。携带有效载荷的选项(例如记录路由)会被回显。开发期间针对监听套接字测得:
| SYN 选项 | SYN-ACK IP 头 | 回显选项 |
|---|---|---|
| 24 个 NOP 字节 | 20 字节(IHL 5) | 无 |
| 记录路由,12 字节 | 32 字节(IHL 8) | 12 字节 |
| 无 | 20 字节(IHL 5) | 无 |
这两个事实对实验室工作都很重要:响应的头部长度告诉你协议栈是否保留了选项块,而正确的解析器从 IHL * 4 开始解析 TCP 头,而不是从偏移 20 开始。
本仓库背后的流传脚本(cve_2026_98432_auto.zip)附带了它自己的运行日志。该日志中每个“成功”的 SYN-ACK 都有 reply_sport=257 和 tcp_seq=16843009。这些正是 NOP 填充字节:0x0101 是 257,0x01010101 是 16843009。解析器在数据包中任意位置扫描标志位,最终落在偏移 55,即选项填充内部。对该 84 字节数据包的正确解析会得到 IHL 5、协议 TCP,以及一个标志字节为 0x00 的 TCP 段——没有 SYN 也没有 ACK。
有效载荷是脚本自己构造的 SYN(偏移 20 处的 0x4b)。外层头部属于内核:脚本从未设置 IP_HDRINCL,因此 Linux 构建了自己的 IPv4 头,并将构造的数据包作为有效载荷。它构造的任何内容都从未作为数据包被传输。两个失败叠加在一起:数据包从未按设计发出,而解析器将填充字节读取为响应。
在没有密钥的情况下,dork 阶段是无效的。它读取 --shodan-key 或 SHODAN_KEY;两者都没有时,它将 192.168.56.101(VirtualBox 仅主机默认地址)写入 targets.txt 并继续运行。有密钥时,它会向 Shodan 查询公共主机并向它们发起攻击,这没有任何实验室工具需要,而且会导致仓库被移除。
synipopt 保留了单数据包线上的思路,移除了目标定位阶段,用头部偏移解析替换了启发式解析器,并将旧的日志值转化为 tests/test_packet.py 中的回归测试。
CVE-2026-98432,Linux 内核 IPv4 网络,释放后使用,Linux CNA 给出的 CVSS 3.1 评分为 9.8。Red Hat 将其评为严重(RHSB-2026-016),并将 RHEL 8、9、10 和 OpenShift 列为受影响产品。
修复提交中描述的竞态:tcp_v4_syn_recv_sock() 将 ireq->ireq_opt 转移给子套接字(newinet->inet_opt)时未进行复制。另一个 CPU 可以处理同一请求套接字的重传 SYN,并从 tcp_check_req() 发送 SYN-ACK。tcp_v4_send_synack() 和 inet_csk_route_req() 仅在 rcu_read_lock() 下读取 ireq->ireq_opt,随后 ip_build_and_send_pkt() 和 ip_options_build() 会两次读取 opt->optlen。由于请求套接字在没有监听锁的情况下被处理(提交 079096f103fa),子套接字可能在 SYN-ACK 仍在构建时被释放。子套接字缺少 SOCK_RCU_FREE,而 inet_sock_destruct() 使用普通的 kfree() 释放 inet_opt,因此 ip_options_build() 可能读取已释放的内存。
主线修复是提交 e3f33b0a1d89d738e2b913dab519226eaf76e15b,“ipv4: free inet_opt and ireq_opt after an RCU grace period”(Eric Dumazet,合并于 2026-10-05)。它在 inet_sock_destruct() 和 tcp_v4_reqsk_destructor() 中将 kfree() 替换为 kfree_rcu()。同一提交还指出了通过请求套接字迁移(net.ipv4.tcp_migrate_req=1 或 BPF reuseport 程序)的第二条路径,其中 inet_reqsk_clone() 在旧请求套接字及其克隆之间共享 ireq_opt。
Red Hat 的临时缓解措施会丢弃携带 IP 选项的入站 IPv4 TCP 数据包:
table inet cve_2026_98432 {
chain input {
type filter hook input priority -310; policy accept;
ip protocol tcp ip hdrlength > 5 counter drop
}
}
--compare 正是针对该过滤器进行测试:携带选项的 SYN 得不到响应,而普通 SYN 会收到回复。
来源:
线上探测无法证明内存损坏。它们能显示的是:主机是否响应携带 IP 选项的 SYN,SYN-ACK 是否回显这些选项,以及路径中是否存在 hdrlength 过滤器。
uname -r。tcpdump -ni eth0 'tcp and (ip[0] & 0x0f) > 5'sudo synipopt --target VM_IP --dport 22 --retries 5 --compare --log run.jsonlreply_kind 对响应进行分类,tcp 块包含解析后的头部。ip_options_len 显示回显,对于带负载的选项块(记录路由、时间戳)会出现;纯 NOP 填充会被编译器优化掉,返回时表现为 20 字节头部。dmesg。线上只能显示数据包处理情况。测试你拥有或获授权测试的主机。默认防护会拒绝 RFC 1918、回环、链路本地、CGNAT 和文档范围之外的地址。--allow-public 用于在你控制的主机上进行的授权测试。向第三方发送构造的 SYN 不是本工具的用途。
python3 -m pytest -q
测试套件涵盖校验和属性、构建与解析往返、选项长度限制、响应分类(SYN-ACK、RST、ICMP、错误流)、CLI 防护和试运行,以及作为回归测试夹具的原始日志数据包。CI 运行 Python 3.10 和 3.12。
源自一个流传的自动利用脚本,该脚本将 Shodan 搜索与原始 SYN 竞态探测相结合。本次重写保留了线级思路,移除了大规模目标选择阶段,并用头部偏移解析替换了启发式解析器。原始运行日志值保留为测试夹具。
文档遵循 antislop 和 humanizer 写作规则。
MIT,参见 LICENSE。