概要
先日、Linuxカーネルの脆弱性「CVE-2026-98432」について、原因と暫定対策を記事にしました。
その後、2026年10月30日付で、Red HatおよびCIQからnftablesを利用した緩和策が公開されました。
今回の脆弱性は、IPv4 TCP接続の処理中に解放済みメモリへアクセスするUse-After-Free(UAF)に起因するものです。
攻撃者がIPv4オプションを含むTCPパケットを送信することで、問題のある処理経路に到達する可能性があります。
そこで、IPv4オプション付きTCPパケットをカーネルのTCP処理に到達する前に破棄するというのが、今回の緩和策です。
参考資料:
CIQの資料では、MPTCPを無効化した状態でも通常のTCP通信から脆弱な処理経路に到達できることが確認されています。そのため、MPTCPの無効化だけに頼らず、nftablesによる通信制御を追加することにしました。
今回は、実際に運用しているサーバに暫定ルールを追加し、systemdによる永続化の設定まで行った記録です。
環境
- OS:Ubuntu 24.04 LTS
- Webサーバ:Apache
- ファイアウォール:UFW
- IPアドレス制御:ipset
- 不正アクセス対策:fail2ban
- iptables:v1.8.10(nf_tables)
- 作業日:2026年10月31日
筆者環境では、UFWとipsetを組み合わせたアクセス制御を行っています。
今回の作業前に、nftablesのルールセットを確認しました。
sudo nft list ruleset | wc -l
結果は、
6089
6,089行。
この時点で、今回の作業方針はほぼ決まりました。既存のファイアウォールには、できる限り手を加えないことです。
実際、iptablesのバージョンを確認すると、
sudo iptables -V
iptables v1.8.10 (nf_tables)
となっていました。また、nftablesのテーブル一覧は以下のとおりです。
sudo nft list tables
table ip6 filter
table ip filter
table ip nat
table ip6 nat
table ip raw
table inet f2b-table
UFWなどがiptables-nftを通じて管理しているテーブルに加え、fail2ban用の独立したテーブルも存在しています。
今回の緩和策は、これらとは別に専用テーブルを作成する方針としました。
フェイズゼロ:政治交渉
以前の記事でも触れましたが、システムの保守作業には技術的な準備だけでなく、作業時間の確保や関係者との調整が必要です。
今回も例外ではありません。
ただし、今回の作業では、もう一つ交渉相手がいました。
自分自身です。
既に稼働しているファイアウォールの設定を変更するのは、できれば避けたい作業です。
しかも、SSHで接続しているサーバのネットワーク制御を変更するとなれば、設定を間違えた瞬間に自分自身を締め出す可能性があります。
そこで、今回の作業では以下を徹底しました。
- 既存のUFW・ipset・fail2banの設定は変更しない。
- nftablesにCVE対策専用のテーブルを作成する。
- 変更前のルールセットを保存する。
- シリアルコンソールを確保する。
- 問題が発生した場合は、専用テーブルだけを削除する。
特に重要なのが、シリアルコンソールです。
SSH接続を失った場合でも、ネットワーク経由ではない復旧手段を確保してから作業に入りました。
退路を確保してから前進する。
今回は、これが最も重要な準備でした。
さっくりとした手順
- 作業ディレクトリへ移動
cd /home/hoge/work/cve-2026-98432
任意のディレクトリを指定します。
- 既存ルールセットをバックアップ
sudo nft -a list ruleset > nftables-before-cve-2026-98432.txt - テーブル一覧をバックアップ
sudo nft list tables > nftables-tables-before.txt - 既存のCVE対策用テーブルを確認
sudo nft list table inet cve_2026_98432 - 暫定ルールの設定ファイルを作成
nano cve-2026-98432.nft - 構文チェック
sudo nft --check --file cve-2026-98432.nft - 暫定ルールを適用
sudo nft --file cve-2026-98432.nft - 適用結果を確認
sudo nft -a list table inet cve_2026_98432 - 永続化用の設定ファイルを作成
sudo nano /etc/nftables/cve-2026-98432.nft - systemdユニットを作成
sudo nano /etc/systemd/system/cve-2026-98432-nft.service - 永続化用設定の構文チェック
sudo nft --check --file /etc/nftables/cve-2026-98432.nft - systemdユニットの検証
sudo systemd-analyze verify /etc/systemd/system/cve-2026-98432-nft.service - systemdの設定を再読み込み
sudo systemctl daemon-reload - 自動起動を有効化
sudo systemctl enable cve-2026-98432-nft.service - 自動起動の登録状態を確認
systemctl is-enabled cve-2026-98432-nft.service - 既存の防御機構を確認
sudo ufw statussudo ipset list -namesudo fail2ban-client status
作業詳細
1. 既存のルールセットをバックアップ
まずは、作業ディレクトリを用意しました。
今回の作業ディレクトリは以下です。任意のディレクトリを指定してください。
cd /home/hoge/work/cve-2026-98432
現在のルールセットを保存します。
sudo nft -a list ruleset > nftables-before-cve-2026-98432.txt
続いて、行数を確認しました。
wc -l nftables-before-cve-2026-98432.txt
6089 nftables-before-cve-2026-98432.txt
既存の6,089行を保存できています。
次に、テーブル一覧も保存します。
sudo nft list tables > nftables-tables-before.txt
ファイルの状態は以下でした。
nftables-before-cve-2026-98432.txt 239547 bytes
nftables-tables-before.txt 94 bytes
これで、変更前の状態を記録できました。なお、これらのファイルは復旧時の参照用です。
iptables-nftで管理されているテーブルも含まれるため、バックアップ全体を無条件に読み戻すことはしません。
2. nftablesの専用テーブルを作成
まず、同名のテーブルが存在しないことを確認します。
sudo nft list table inet cve_2026_98432
続いて、作業ディレクトリに設定ファイルを作成しました。
nano cve-2026-98432.nft
内容は以下です。
table inet cve_2026_98432 {
chain prerouting {
type filter hook prerouting priority -300; policy accept;
ip hdrlength > 5 meta l4proto tcp counter drop
}
}
それぞれの意味は以下のとおりです。
table inet cve_2026_98432:今回のCVE対策専用テーブルhook prerouting:受信パケットの早い段階で評価priority -300:raw優先度ip hdrlength > 5:IPv4ヘッダーが通常の20バイトを超えるパケットを対象meta l4proto tcp:TCP通信を対象counter drop:該当パケットをカウントして破棄
ここで重要なのは、IPv4のIPオプションとTCPオプションは異なるという点です。
今回のルールは、TCPタイムスタンプなどの一般的なTCPオプションを一律に遮断するものではありません。
一方、IPv4オプションを使用する正当なTCP通信は遮断されるため、適用先の通信要件を考慮する必要があります。
3. 構文チェック
設定ファイルを保存したら、構文チェックを実施します。
sudo nft --check --file cve-2026-98432.nft
エラーは表示されませんでした。終了ステータスも確認します。
echo $?
0
構文チェック成功です。この段階では、まだ通信制御は変更されていません。
4. シリアルコンソールを確保
実際に適用する前に、vpsのシリアルコンソールへ接続しました。
今回の作業で最も避けたいのは、SSH接続を失ってサーバへアクセスできなくなることです。
現在のUFWでは、以下のポートを許可しています。
80/tcp
443/tcp
22/tcp
今回のnftablesルールはポート番号による判定ではないため、これらのポートにも条件が一致すれば適用されます。
そのため、SSHセッションを維持したまま作業し、必要になればシリアルコンソールから切り戻せるようにしました。
5. 暫定ルールを適用
準備ができたところで、設定を適用します。その前に:深く深呼吸を3回ぐらいしましょう。ここから先、タイポ、コマンド忘れ、全てが致命的になり得ます。
sudo nft --file cve-2026-98432.nft
エラーは表示されませんでした。続いて、適用結果を確認します。
sudo nft -a list table inet cve_2026_98432
結果は以下です。
table inet cve_2026_98432 { # handle 10
chain prerouting { # handle 1
type filter hook prerouting priority raw; policy accept;
ip hdrlength > 5 meta l4proto tcp counter packets 0 bytes 0 drop # handle 2
}
}
設定ファイルではpriority -300と指定しましたが、表示上はpriority rawとなっています。これはnftablesが数値を定義済みの優先度名で表示しているためです。
また、
counter packets 0 bytes 0
となっています。この時点では、遮断条件に一致したパケットはありませんでした。
6. Webサイトの動作確認
適用後、自サイトへアクセスし、記事の登録まで行いました。
HTTPSによるアクセス、ログイン状態の維持、記事の登録と保存が正常に行えています。
単純なHTTP応答だけでなく、普段利用しているWebアプリケーションの操作が継続できることを確認しました。
少なくとも、今回確認した範囲ではWebサイトの利用に支障は発生していません。
永続化
1. 永続化用の設定ファイルを作成
暫定ルールの動作を確認できたため、再起動後にも読み込めるようにします。ただし、既存のUFWやipsetを管理している仕組みには手を加えません。
今回は、CVE対策専用の設定ファイルとsystemdユニットを作成しました。
sudo mkdir -p /etc/nftables
sudo nano /etc/nftables/cve-2026-98432.nft
内容は以下です。
add table inet cve_2026_98432
delete table inet cve_2026_98432
table inet cve_2026_98432 {
chain prerouting {
type filter hook prerouting priority -300; policy accept;
ip hdrlength > 5 meta l4proto tcp counter drop
}
}
冒頭の、
add table inet cve_2026_98432
delete table inet cve_2026_98432
は、既存の専用テーブルが存在する場合でも、同じ名前のテーブルを作り直せるようにするためのものです。
削除対象はcve_2026_98432テーブルに限定しています。
UFWやfail2banのテーブルには触れません。
2. systemdユニットを作成
続いて、専用のsystemdユニットを作成します。
sudo nano /etc/systemd/system/cve-2026-98432-nft.service
内容は以下です。
[Unit]
Description=Temporary nftables mitigation for CVE-2026-98432
Wants=network-pre.target
Before=network-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/nftables/cve-2026-98432.nft
ExecStop=-/usr/sbin/nft delete table inet cve_2026_98432
[Install]
WantedBy=multi-user.target
このユニットでは、起動時に専用のnftables設定ファイルを読み込みます。停止時には、今回追加したテーブルだけを削除します。
RemainAfterExit=yesを指定しているため、ルールの読み込みが完了した後もsystemd上ではサービスを有効な状態として扱います。
3. 設定ファイルの構文チェック
まず、nftables設定ファイルを検証します。
sudo nft --check --file /etc/nftables/cve-2026-98432.nft
終了ステータスを確認しました。
echo $?
0
正常終了です。
続いて、systemdユニットも検証します。
sudo systemd-analyze verify /etc/systemd/system/cve-2026-98432-nft.service
4. systemdに登録
ユニット定義を再読み込みします。この作業の前に、もう4回ほど深呼吸をして、飲み物を何か飲みましょう。お茶菓子などがあれば更に最高です。
システムをいじる作業は全て自分の指先に全責任が伴います。
sudo systemctl daemon-reload
続いて、自動起動を有効化します。
sudo systemctl enable cve-2026-98432-nft.service
ここでは--nowを付けませんでした。
既に手動で適用したルールが動作しているため、現時点で専用テーブルを作り直す必要がないからです。
最後に、自動起動の登録状態を確認します。
systemctl is-enabled cve-2026-98432-nft.service
結果は、
enabled
となりました。
これで、systemdによる自動起動の登録まで完了です。
なお、再起動後に実際にルールが復元されるかは、まだ確認していません。
既存の防御機構を確認
今回の作業で最も気になっていたのは、既存のUFW・ipset・fail2banへの影響です。
設定ファイルを作成し、自動起動を登録した後、改めて状態を確認しました。
1. nftables
sudo nft list table inet cve_2026_98432
table inet cve_2026_98432 {
chain prerouting {
type filter hook prerouting priority raw; policy accept;
ip hdrlength > 5 meta l4proto tcp counter packets 0 bytes 0 drop
}
}
専用テーブルは維持されています。
2. UFW
sudo ufw status
状態: アクティブ
To Action From
-- ------ ----
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
22/tcp ALLOW Anywhere
UFWはアクティブのままです。
既存の許可ポートにも変更はありません。
3. ipset
sudo ipset list -name
ufw-blocklist
既存のipsetが存在していることを確認できました。
4. fail2ban
sudo fail2ban-client status
Status
|- Number of jail: 2
`- Jail list: sshd, ufw
fail2banも、既存の2つのjailを維持しています。
これらの確認から、少なくともサービスの状態と設定の一覧については、作業前の構成が維持されていることを確認できました。
切り戻し
今回の暫定ルールに問題が発生した場合は、専用テーブルを削除します。
一時的にルールを撤去する場合
sudo nft delete table inet cve_2026_98432
この操作で、今回追加したテーブルとチェーンを削除できます。
ただし、systemdの自動起動設定は残るため、次回起動時には再び読み込まれます。
永続化設定も撤去する場合
まず、自動起動を無効化します。
sudo systemctl disable cve-2026-98432-nft.service
続いて、専用テーブルを削除します。
sudo nft delete table inet cve_2026_98432
systemdユニットを削除します。
sudo rm /etc/systemd/system/cve-2026-98432-nft.service
nftables設定ファイルも削除します。
sudo rm /etc/nftables/cve-2026-98432.nft
最後に、systemdの設定を再読み込みします。
sudo systemctl daemon-reload
今回追加した設定だけを撤去するため、既存のUFW・ipset・fail2banの設定はそのまま維持できます。
今後の確認事項
今回の作業では、以下まで完了しています。
- 既存ルールセットのバックアップ
- シリアルコンソールによる復旧経路の確保
- nftables専用テーブルの追加
- 設定ファイルの構文チェック
- Webサイトの動作確認
- systemdユニットの作成
- 自動起動の有効化
- UFW・ipset・fail2banの状態確認
残っているのは、再起動後の動作確認です。
次回の再起動後には、以下を確認します。
systemctl is-active cve-2026-98432-nft.service
sudo nft list table inet cve_2026_98432
activeと表示されるだけでなく、実際に専用テーブルが存在していることを確認します。
また、今回のルールはIPv4オプション付きTCPパケットを遮断する暫定措置です。
CIQが公開した検証結果はRocky Linux系の環境を対象としており、筆者環境のUbuntu 24.04 LTSで同等の攻撃パケットを用いた遮断試験までは実施していません。
そのため、今回確認できたのはルールの登録と通常通信の継続までです。
修正済みカーネルが提供された段階で更新を行い、不要になった暫定ルールを撤去する予定です。
おわりに
今回の作業で、最も神経を使ったのはnftablesの設定そのものではありません。
既に稼働している防御機構を壊さずに、新しい防御を追加することでした。
筆者環境では、UFW・ipset・fail2banを組み合わせてアクセス制御を行っています。その結果、nftablesのルールセットは6,089行に達していました。
そこへ緊急の脆弱性対策を追加するのですから、作業を始める前から気が重くなるのも無理はありません。
そこで、今回は既存のルールセットを編集せず、専用テーブルを追加する方式を採用しました。
さらに、シリアルコンソールを確保し、問題が発生した場合には今回追加したテーブルだけを削除できるようにしています。
結果として、既存のUFW・ipset・fail2banの設定を変更することなく、暫定緩和策の導入と永続化の登録まで進められました。
今回の作業で改めて感じたのは、システムの保守とは、何かを変更する技術だけでなく、変更しなくてよいものを見極める判断でもあるということです。
脆弱性を塞ごうとして、既存の防御機構を壊してしまっては意味がありません。
そして、その判断を支えるのがバックアップであり、切り戻し手順であり、シリアルコンソールという最後の退路です。
作業を終えた後の感想を一言で表すなら、
「心臓に悪い作業だった」
でしょうか。
とはいえ、これで修正済みカーネルが提供されるまでの間、攻撃経路を減らすための対策を一つ追加できました。
今後もカーネルの修正状況を確認しながら、必要な対応を続けていきます。

