
IPv4 SYNとIPオプションを使用し、1回の実行につき1つのラボターゲットを対象とする、CVE-2026-98432の研究用。ヘッダーオフセットパーサー、リグレッションテスト、そして壊れた自動エクスプロイターのポストモーテム。
2026年10月に修正されたLinuxカーネルIPv4 TCPのuse-after-free、CVE-2026-98432に対する単一ターゲットプローブです。
IPv4オプションを運ぶIPv4 SYNを構築し(デフォルトでは24バイトのNOPで、IHLを11に引き上げます)、それを1つのホストに送信し、パターンマッチングではなくヘッダーオフセットによって応答を読み取ります。パケットロジックはオフラインでも動作します。ドライランは作成したパケットを出力して再解析し、16進アナライザーはキャプチャしたトラフィックを解析し、テストスイートはrootもネットワークも必要としません。
ターゲットリストの処理、Shodan統合、バッチモードはありません。1回の実行につき1つのアドレスで、--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)"
ラボのVMをプローブし、オプションを運ぶ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オプションなしで返ってきます。ペイロードを運ぶオプション、例えばRecord Routeはエコーバックされます。開発中にリッスンソケットに対して測定した結果:
| SYNオプション | SYN-ACK IPヘッダー | エコーバックされたオプション |
|---|---|---|
| 24 NOPバイト | 20バイト(IHL 5) | なし |
| Record Route、12バイト | 32バイト(IHL 8) | 12バイト |
| なし | 20バイト(IHL 5) | なし |
どちらの事実もラボ作業にとって重要です。応答のヘッダー長はスタックがオプションブロックを保持したかどうかを示し、正しいパーサーはTCPヘッダーをオフセット20ではなくIHL * 4から開始します。
このリポジトリの元になった出回っているスクリプト(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ヘッダーを構築し、作成したパケットをペイロードとして扱いました。構築したものはパケットとして送信されることはありませんでした。2つの失敗が重なっています。パケットは設計どおりに出発せず、パーサーはフィラーバイトを応答として読み取りました。
dorkステージはキーなしでは不活性でした。--shodan-keyまたはSHODAN_KEYを読み取り、どちらもない場合は192.168.56.101(VirtualBoxのホストオンリーデフォルト)をtargets.txtに書き込んで続行しました。キーがある場合は、Shodanにパブリックホストを問い合わせてそれらに向けて発砲しました。これはどのラボツールにも必要なく、リポジトリが削除される原因になります。
synipoptは単一パケットのワイヤー上のアイデアを維持し、ターゲティングステージを削除し、ヒューリスティックパーサーをヘッダーオフセット解析に置き換え、古いログの値をtests/test_packet.pyの回帰テストに変えています。
CVE-2026-98432、LinuxカーネルIPv4ネットワーキング、use-after-free、Linux CNAによるCVSS 3.1 9.8。Red HatはこれをCritical(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 を2回読み取ります。リクエストソケットはリスナーロックなしで処理されるため (コミット 079096f103fa)、SYN-ACK の構築中に子ソケットが解放される可能性があります。子ソケットには SOCK_RCU_FREE がなく、inet_sock_destruct() は inet_opt を単純な kfree() で解放していたため、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 プログラム) を経由する2つ目の経路にも言及しています。そこでは 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 はエコーを示し、これはペイロードを持つオプションブロック (record route、timestamp) で現れます。純粋な 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 を参照してください。