カーネルを乗っ取られたら「終わり」。署名検証によるLKM攻撃の封じ込め
現場でインシデント対応をしていると、脆弱なWebアプリのパッチ当てに追われるエンジニアをよく見かける。だが、アプリ層の脆弱性はあくまで「入り口」の話だ。真の悪夢は、攻撃者がカーネル空間に侵入し、ルートキットを仕込んでシステム全体を「自分たちの所有物」に変えた瞬間に始まる。
今回は、Linuxサーバーの心臓部であるカーネルを守るための最も実効性の高い防壁、「カーネルモジュールの署名検証」について語ろう。
なぜ「カーネルモジュールのロード制限」が重要なのか
攻撃者がroot権限を奪取した際、真っ先にやりたいことは「自分の痕跡を消すこと」と「バックドアを永続化すること」だ。ここで彼らが使うのが、insmod や modprobe コマンドによる不正なカーネルモジュール(LKM: Loadable Kernel Module)の挿入だ。
一度LKMがロードされれば、システムコールをフックしてプロセスを隠蔽したり、ネットワークパケットを透過的に盗聴したりすることが可能になる。OSのログや ps コマンドですら、攻撃者によって改ざんされた結果を表示させられるため、何が起きているか全く見えなくなる。これが「カーネルレベルでの敗北」だ。
攻撃者の視点:PoCの仕組み
攻撃者は通常、以下のような手順で足場を固める。
1. コンパイル: 悪意ある evil.c をターゲットのカーネルバージョンに合わせてコンパイルし、evil.ko を作成。
2. 挿入: insmod evil.ko を実行し、カーネルのメモリ空間へコードを注入。
3. 隠蔽: カーネルの module_list から自身のモジュールをリンク解除し、lsmod に表示されないようにする。
これを防ぐ唯一の強力な手段が、「カーネル署名検証(Kernel Module Signing)」の強制だ。これを有効にすると、カーネルは「信頼できる署名」がないモジュールのロードを一切拒否するようになる。
実装:カーネル署名検証の有効化手順
まず、現在のシステムでカーネルモジュールの署名が強制されているか確認しよう。
# 現在のカーネル設定を確認
sysctl kernel.modules_disabled
# もし0なら、実行時にモジュールをロードできてしまうため危険
1. GRUBの設定変更
カーネル起動オプションに署名検証を強制するパラメータを追加する。/etc/default/grub を編集しよう。
# /etc/default/grub を編集
GRUB_CMDLINE_LINUX_DEFAULT="... module.sig_enforce=1"
# 設定を反映(OSによってコマンドは異なる。Ubuntu系なら以下)
sudo update-grub
この module.sig_enforce=1 を指定することで、署名のないモジュール、あるいは改ざんされたモジュールのロードがカーネルレベルでブロックされる。
2. セキュアブートとの連携
可能であれば、UEFIの「セキュアブート」を有効にすることを強く推奨する。セキュアブートが有効であれば、カーネル自体が署名されたものしか起動せず、その後のモジュール署名検証とセットで「信頼の連鎖(Chain of Trust)」が完成する。
運用上の注意点:自作モジュールはどう扱うか?
「自作のカーネルモジュール(ドライバ等)はどうすればいいのか?」という質問をよく受ける。答えは簡単だ。「自分で署名すればいい」。
以下の手順で、プライベートキーを作成し、モジュールに署名を行う。
# 1. 署名用の鍵ペアを生成
openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 -config x509.genkey -outform DER -out signing_key.x509 -keyout signing_key.priv
# 2. 生成された鍵を使ってモジュールに署名
/usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 ./signing_key.priv ./signing_key.x509 ./my_driver.ko
最後に:セキュリティは「多層」で考える
カーネル署名検証は強力だが、これだけで万全だとは決して思わないこと。もしカーネルの脆弱性(例: Dirty Pipeのような特権昇格脆弱性)が見つかれば、署名検証をバイパスする手法が編み出される可能性もゼロではない。
1. カーネルのアップデートを怠らない(最新版は常にリスクが低い)。
2. 不要なモジュールを削除する(そもそもロードさせないのが最強の防御)。
3. AppArmorやSELinuxでプロセスを制限する(特権プロセスであっても、できることを最小化する)。
セキュリティを語る上で「これだけで完璧」な設定は存在しない。だが、この「カーネルの入り口を塞ぐ」という設計思想を導入するだけで、攻撃者にとってのコストは劇的に跳ね上がる。泥臭いかもしれないが、こうした地道な設定の積み重ねが、組織をインシデントから守り抜く唯一の道なのだ。
今日から、君の管理下のサーバーで module.sig_enforce が有効になっているか、今すぐ確認してほしい。それがプロの仕事だ。
コメント